Spring?Service中的@Service注解的使用小結
@Service注解是Spring框架中用于標識業(yè)務邏輯層(Service層)的注解。它是Spring組件掃描機制的一部分,表明這個類包含業(yè)務邏輯,并且應該由Spring容器管理為一個Spring Bean。它與@Component類似,都是標識一個類為Spring管理的Bean,但@Service通常用于專門標識業(yè)務邏輯類。
1. @Service的基本功能
@Service是一個特殊的@Component,它本質上是@Component的派生注解。通過使用 @Service,我們可以告訴Spring容器去自動掃描和注冊這些類為Bean,供依賴注入使用。
@Service
public class UserService {
public User getUserById(Long id) {
// 業(yè)務邏輯代碼
return new User(id, "John Doe");
}
}
在這個例子中,UserService類被@Service注解標識,Spring會將它作為Bean注冊到應用上下文中。
2. 如何與@Autowired結合使用
@Autowired注解用于將Spring容器中的Bean自動注入到其他類的字段、構造器或方法中。@Autowired可以用于控制器、服務層或其他任何需要依賴注入的地方。
代理對象的獲取是通過 Spring 的依賴注入機制實現(xiàn)的。你在使用的業(yè)務類(如 UserService)被 Spring 掃描到并管理為 Bean 后,Spring 會自動為它生成代理對象,并將該代理對象注入到你需要的地方。
這里依賴注入的是代理對象。
2.1常見的@Autowired用法
使用場景:
@Autowired 一般用于注入其他 Spring 容器管理的 Bean,適用于以下場景:
- 服務類之間的依賴:當一個服務類依賴于另一個服務類時。
- 控制器類依賴服務類:在 Web 應用中,控制器通常需要調用服務類。
- 服務類依賴數(shù)據訪問層:使用
@Autowired將數(shù)據訪問層(例如Repository類)注入到服務類中。
2.1.1構造器注入(推薦方式)
使用構造器注入能確保依賴在類實例化時就被正確注入,并且方便進行單元測試。
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
}
@Autowired注解使用在構造函數(shù)前。
當你在構造函數(shù)的參數(shù)中將經過@Service注解的類的對象作為參數(shù),spring容器會自動幫你創(chuàng)建這個類(UserService)的實例,然后把這個創(chuàng)建好的實例對象引用給該構造函數(shù)所攜帶的參數(shù) userService,相當于就是自動進行了UserService userService =new UserService();
這里的依賴注入更精確的說,是對構造函數(shù)的參數(shù)進行依賴注入。
然后現(xiàn)在userService就是被創(chuàng)建好了的對象,然后再將這個對象的值賦值給這個類的成員變量private final UserService userService(這里的this.userService就是指這個類內部的變量private final UserService userService中的userService,為什么要這樣做呢?因為你當時依賴注入的對象是構造函數(shù)參數(shù)中的對象,就會導致它是作為局部變量,一旦構造函數(shù)執(zhí)行完畢,這些局部變量就會被釋放,所以你需要有一個地方來存儲這個實例(保存到類的成員變量中),以便在類的其他方法中使用它。這就是為什么要在@Autowired注解依賴注入之前先定義private final UserService userService這個成員變量。
2.1.2字段注入
使用@Autowired直接注入到類的成員變量中。這是最常見但不推薦的方式,因為它使得依賴關系不那么顯式,并且在單元測試中可能不太靈活。
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
}
@Autowired注解使用在類的成員變量之前。
直接對你所創(chuàng)建的成員變量進行依賴注入,相當于private UserService userService = new UserService();
2.1.3Setter注入
通過提供一個setter方法來注入依賴,盡管使用頻率較低,但它可以在某些需要動態(tài)設置依賴的場景中使用。
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
}
@Autowired注解使用在類的setter方法之前。
這種方式其實跟構造器注入很相似,@Autowired注解都是用在函數(shù)之前,依賴注入都是對方法中的參數(shù)進行依賴注入。
只不過唯一的區(qū)別就是構造器注入是在你創(chuàng)建對象的時候會自動對成員變量userService進行賦值,而這中方式則是在你調用userService的setter方法時才會對userService進行賦值。
所以這種依賴注入方式一般不用。
2.1.4 不需要@Autowired注解的情況
1. 構造函數(shù)注入
從Spring 4.3開始,如果一個Bean只有一個構造函數(shù),Spring會自動使用該構造函數(shù)進行依賴注入,無需@Autowired注解。
@Service
public class UserService {
private final UserRepository userRepository;
// 不需要@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}2. 單一實現(xiàn)的接口
如果一個接口只有一個實現(xiàn)類,Spring會自動將這個實現(xiàn)類注入到需要該接口的地方,無需額外配置。
public interface MessageService {
String getMessage();
}
@Service
public class EmailService implements MessageService {
public String getMessage() {
return "Email message";
}
}
@Controller
public class MessageController {
private final MessageService messageService;
// 自動注入EmailService,無需@Autowired
public MessageController(MessageService messageService) {
this.messageService = messageService;
}
}3. @Configuration類中的@Bean方法
在@Configuration類中定義的@Bean方法可以直接使用其他Bean作為參數(shù),Spring會自動注入。
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
// 創(chuàng)建并返回DataSource
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
// Spring自動注入上面定義的dataSource
return new JdbcTemplate(dataSource);
}
}ResponseEntity<User>是什么類型?
ResponseEntity<User>是一個Spring框架中的泛型類,用于構建HTTP響應。它表示一個封裝了HTTP響應的實體,包含了HTTP狀態(tài)碼、響應頭、以及響應體。
泛型參數(shù)
<User>指的是響應體的類型。在這個例子中,<User>表示HTTP響應體中會返回一個User類型的對象。ResponseEntity的主要功能是可以更靈活地控制HTTP響應:- 狀態(tài)碼:你可以使用
ResponseEntity指定HTTP狀態(tài)碼,比如200(OK)、404(Not Found)等。 - 響應頭:你可以添加自定義的響應頭。
- 響應體:響應的內容可以是任何類型,在這個例子中是
User類型的對象。
- 狀態(tài)碼:你可以使用
ResponseEntity的構造方法和常用方法:
ResponseEntity.ok(T body):返回200狀態(tài)碼,響應體是傳入的對象。ResponseEntity.status(HttpStatus status):自定義狀態(tài)碼,結合.body(T body)可以設置響應體。ResponseEntity.notFound():返回404狀態(tài)碼。ResponseEntity.noContent():返回204狀態(tài)碼,不帶響應體。
2.2 @Service與@Autowired結合的典型場景
場景1:控制層注入Service層
在Spring MVC的控制層(@Controller或@RestController)中,業(yè)務邏輯通常委托給服務層處理。這種場景下,控制層會通過@Autowired注解注入@Service標識的類。
// UserService.java
@Service
public class UserService {
public User getUserById(Long id) {
return new User(id, "John Doe");
}
}
// UserController.java
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
}
- 在
UserController中,UserService通過@Autowired注解進行構造器注入,確保UserController可以調用UserService中的業(yè)務邏輯方法。
場景2:服務層之間相互調用
在復雜的業(yè)務場景中,一個Service類可能會依賴另一個Service類,這時也可以使用@Autowired進行注入。
// OrderService.java
@Service
public class OrderService {
public String processOrder(Long orderId) {
return "Order processed: " + orderId;
}
}
// PaymentService.java
@Service
public class PaymentService {
private final OrderService orderService;
@Autowired
public PaymentService(OrderService orderService) {
this.orderService = orderService;
}
public String makePayment(Long orderId) {
String result = orderService.processOrder(orderId);
return "Payment completed for " + result;
}
}
PaymentService依賴于OrderService,通過構造器注入的方式,將OrderService作為依賴注入到PaymentService中。
2.3 依賴注入的高級使用場景
2.3.1 使用@Qualifier區(qū)分多個Bean
在某些情況下,如果Spring容器中有多個同類型的Bean(例如多個@Service),需要通過@Qualifier注解來明確指定注入的具體Bean。
@Service("basicOrderService")
public class BasicOrderService implements OrderService {
// 實現(xiàn)邏輯
}
@Service("advancedOrderService")
public class AdvancedOrderService implements OrderService {
// 實現(xiàn)邏輯
}
@Service
public class PaymentService {
private final OrderService orderService;
@Autowired
public PaymentService(@Qualifier("basicOrderService") OrderService orderService) {
this.orderService = orderService;
}
// 業(yè)務邏輯
}
- 通過
@Qualifier("basicOrderService"),我們指定注入的具體實現(xiàn)類BasicOrderService。
2.3.2結合@Primary注解
@Primary注解用于標識在多個相同類型的Bean中優(yōu)先注入某個Bean。如果沒有使用@Qualifier指定Bean,Spring會注入@Primary標注的Bean。
@Service
@Primary
public class BasicOrderService implements OrderService {
// 實現(xiàn)邏輯
}
@Service
public class AdvancedOrderService implements OrderService {
// 實現(xiàn)邏輯
}
BasicOrderService標注了@Primary,因此在沒有指定@Qualifier的情況下,Spring會優(yōu)先注入BasicOrderService。
2.4 @value注解進行單個屬性的依賴注入
2.4,1 基本用法:從 application.properties 中讀取值
步驟:
- 在
application.properties文件中定義鍵值對。 - 使用
@Value注解將對應的值注入到類的字段中。
示例:
application.properties 文件
url=http://example.com port=8080 enableFeature=true
使用 @Value 注解:
@Service
public class MyService {
@Value("${url}")
private String appUrl;
@Value("${port}")
private int port;
@Value("${enableFeature}")
private boolean enableFeature;
}
2.4.2 默認值
在某些情況下,如果配置文件中沒有定義相應的屬性值,可以使用 @Value 指定默認值,避免出現(xiàn) null 值。
示例:
@Service
public class MyService {
// 如果myapp.url沒有定義,將使用默認值 "http://localhost"
@Value("${myapp.url:http://localhost}")
private String appUrl;
}
3. 與事務管理的結合
在Spring框架中,@Service注解與事務管理的結合是業(yè)務邏輯層非常重要的功能。事務管理保證了在處理多步驟的業(yè)務操作時,數(shù)據的一致性和完整性。例如,在處理銀行轉賬等業(yè)務時,如果其中的一個步驟失敗,整個事務應該回滾,以保證系統(tǒng)中的數(shù)據狀態(tài)正確。Spring通過@Transactional注解結合@Service,為開發(fā)者提供了簡潔而強大的事務管理能力。
3.1 @Transactional注解的作用
@Transactional是Spring用于聲明式事務管理的核心注解。它可以用于類或方法上,指示Spring應該為該類或方法的操作啟用事務。事務管理保證了業(yè)務邏輯中的多個操作要么全部成功,要么全部失敗,這樣可以保證數(shù)據的一致性。
- 當某個方法被標記為
@Transactional時,Spring會將該方法及其中涉及的數(shù)據庫操作(例如插入、更新、刪除)放在一個事務中。 - 如果方法執(zhí)行過程中出現(xiàn)異常,Spring會自動回滾事務,保證數(shù)據庫不會被部分更新。
- 如果方法執(zhí)行成功,事務將提交,數(shù)據庫中的更改將永久生效。
3.2 Spring AOP(面向切面編程)與事務管理
Spring的事務管理機制是通過AOP(面向切面編程)來實現(xiàn)的。以下是事務管理的基本流程:
- AOP代理對象:當Spring啟動時,它會通過AOP為標記了
@Transactional的方法或類生成代理對象。這些代理對象會攔截對方法的調用。 - 事務開始:當代理對象檢測到對標記了
@Transactional的方法的調用時,它會在方法執(zhí)行前開啟一個事務。 - 方法執(zhí)行:方法執(zhí)行過程中,Spring會暫時保存所有對數(shù)據庫的操作,并等待方法的最終結果來決定是否提交或回滾。
- 提交或回滾:如果方法執(zhí)行成功(沒有拋出異常),Spring會提交事務;如果方法執(zhí)行過程中拋出異常,則回滾事務。
3.3 @Transactional的不同應用方式
@Transactional可以作用于類級別或方法級別,它們的行為略有不同。
3.3.1 類級別的@Transactional
如果在類上應用@Transactional,則該類中的所有公共方法都將自動包含事務管理。每次調用該類的公共方法時,Spring都會自動開啟一個事務,方法執(zhí)行成功時提交事務,方法執(zhí)行失敗時回滾事務。
@Service
@Transactional // 應用于整個類
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public void placeOrder(Order order) {
// 保存訂單
orderRepository.save(order);
}
public void cancelOrder(Long orderId) {
// 取消訂單邏輯
Order order = orderRepository.findById(orderId).orElseThrow();
order.setStatus("Cancelled");
orderRepository.save(order);
}
}
在這個例子中,OrderService類中的所有公共方法都會被事務管理。當方法被調用時,事務將自動開啟;如果方法執(zhí)行失?。⊕伋霎惓#?,事務將回滾;如果方法執(zhí)行成功,事務將提交。
3.3.2 方法級別的@Transactional
如果你不想對整個類的所有方法都使用事務管理,可以將@Transactional僅應用于特定方法。在這種情況下,只有被標記的方法會執(zhí)行事務管理。
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional // 僅應用于此方法
public void placeOrder(Order order) {
// 保存訂單的業(yè)務邏輯
orderRepository.save(order);
}
public void cancelOrder(Long orderId) {
// 不使用事務的邏輯
Order order = orderRepository.findById(orderId).orElseThrow();
order.setStatus("Cancelled");
orderRepository.save(order);
}
}
在此例中,placeOrder方法具有事務管理,而cancelOrder方法則不會啟用事務管理。
3.4 @Service與@Transactional的結合
通過將@Transactional與@Service結合使用,Spring能夠自動管理業(yè)務邏輯中的事務。常見的場景是,當業(yè)務邏輯涉及多個數(shù)據庫操作(如插入、更新、刪除)時,如果某個操作失敗,事務可以回滾,從而保證數(shù)據一致性。
示例代碼:
@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, double amount) {
// 1. 扣除付款方賬戶的金額
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
fromAccount.setBalance(fromAccount.getBalance() - amount);
accountRepository.save(fromAccount);
// 2. 增加收款方賬戶的金額
Account toAccount = accountRepository.findById(toAccountId).orElseThrow();
toAccount.setBalance(toAccount.getBalance() + amount);
accountRepository.save(toAccount);
}
}
代碼詳細解釋
@Transactional注解:當調用transferMoney方法時,Spring框架會利用@Transactional注解為該方法開啟一個事務。
- 事務的開始:Spring使用AOP(面向切面編程)技術,在
transferMoney方法調用時攔截并啟動事務。 - 作用:確保該方法內的所有操作作為一個整體,要么全部成功,要么全部失敗。如果方法內部任何地方發(fā)生異常或錯誤,Spring將回滾事務,撤銷已經執(zhí)行的操作。
- 目標:保護數(shù)據一致性,避免銀行轉賬這種操作中一部分成功、一部分失敗的現(xiàn)象
從數(shù)據庫中獲取付款方賬戶:
accountRepository.findById(fromAccountId):從數(shù)據庫中查找ID為fromAccountId的賬戶(付款方)。orElseThrow():如果找不到該賬戶,則拋出異常,事務會因為異常而回滾。
扣除余額:
fromAccount.setBalance(fromAccount.getBalance() - amount):從賬戶的余額中扣除amount,模擬銀行轉賬中的付款操作。
保存更新后的付款方賬戶:
accountRepository.save(fromAccount):將修改后的fromAccount保存回數(shù)據庫,但此時實際的數(shù)據庫操作并沒有立即持久化,數(shù)據仍在事務中,等待事務提交。- 注意:在事務提交前,雖然代碼已經調用
save方法,但對數(shù)據庫的實際寫操作還沒有發(fā)生。
事務提交
- 如果
transferMoney方法執(zhí)行到最后一步,沒有拋出異常,則Spring會自動提交事務,將所有的數(shù)據庫操作(付款方賬戶的扣款和收款方賬戶的存款)一并生效。 - 提交事務時,
accountRepository.save(fromAccount)和accountRepository.save(toAccount)中的數(shù)據庫更改才會被實際寫入到數(shù)據庫中。
3.5 @Transactional的事務屬性
@Transactional提供了多個屬性,用來細化事務的管理行為。常用屬性包括:
3.5.1 propagation(傳播行為)
示例:
定義當前事務方法是否應該運行在一個現(xiàn)有的事務中,或者是否應該創(chuàng)建一個新的事務。
常見的傳播屬性:
REQUIRED:默認值。如果當前有事務,使用當前事務;如果沒有,創(chuàng)建一個新事務。REQUIRES_NEW:每次都會創(chuàng)建一個新事務,并暫停當前事務。SUPPORTS:如果當前有事務,則支持當前事務;如果沒有事務,也可以不使用事務。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAccount(Account account) {
accountRepository.save(account);
}
3.5.2 isolation(隔離級別)
定義數(shù)據庫操作之間的隔離程度,防止臟讀、不可重復讀和幻讀等問題。
常見的隔離級別:
READ_UNCOMMITTED:最低的隔離級別,可能會出現(xiàn)臟讀。READ_COMMITTED:保證讀取的數(shù)據是已提交的,防止臟讀。REPEATABLE_READ:同一個事務中多次讀取相同的數(shù)據結果相同,防止不可重復讀。SERIALIZABLE:最高的隔離級別,防止幻讀。
示例:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateAccount(Account account) {
accountRepository.save(account);
}
3.5.3 timeout(超時)
定義事務的超時時間,如果事務在指定的時間內沒有完成,將會回滾。
示例:
@Transactional(timeout = 30) // 超時時間為30秒
public void performLongRunningTask() {
// 執(zhí)行耗時任務
}
3.5.4 readOnly(只讀事務)
如果事務只進行查詢操作而不進行更新,readOnly = true可以優(yōu)化性能。
示例:
@Transactional(readOnly = true)
public Account getAccount(Long accountId) {
return accountRepository.findById(accountId).orElseThrow();
}
3.5.5 rollbackFor 和 noRollbackFor
默認情況下,Spring的事務管理機制只會在運行時異常(RuntimeException)或Error發(fā)生時回滾事務。而對于受檢異常(Checked Exception),Spring不會自動回滾,除非你顯式地配置rollbackFor屬性來指定。
rollbackFor:指定哪些異常會導致事務回滾,默認情況下,只有未捕獲的運行時異常會導致回滾。noRollbackFor:指定哪些異常不會導致事務回滾。
示例:
@Transactional(rollbackFor = {Exception.class})
public void riskyOperation() throws Exception {
// 執(zhí)行可能拋出受檢異常的操作
}
這個寫法表示只要拋出了Exception類型或它的子類異常,Spring就會回滾事務。
這里的Exception只是代指異常的類型,實際使用的時候要加入實際的異常類型,比如:SQLException(受檢異常)或DataAccessException(運行時異常) 。
Exception.class表示Exception這個類的類對象。在Java中,每個類都有一個對應的類對象(Class對象),它可以通過.class語法獲得。Exception.class表示 Java 的Exception類本身,而不是Exception的一個實例。{Exception.class}是一個包含Exception.class的數(shù)組,這是 Java 數(shù)組的簡寫形式。你可以在數(shù)組中放入多個類類型,像這樣:{Exception.class, IOException.class},表示這是一個由多個類對象組成的數(shù)組。
3.6 @Service與@Transactional的常見使用場景
- 銀行交易:轉賬、提款等涉及多個數(shù)據庫操作的業(yè)務,如果其中一步失敗,整個交易應該回滾。
- 訂單處理:在電商系統(tǒng)中,訂單的創(chuàng)建、庫存的減少、支付的處理等步驟應該作為一個整體事務來執(zhí)行。
- 批量更新:批量插入、更新、刪除數(shù)據的操作需要在一個事務中執(zhí)行,以保證操作的原子性。
4. 作用域與生命周期
4.1作用域(Scope)
作用域決定了Spring容器如何管理Bean的實例。Spring默認會為每個@Service Bean分配一個單例作用域(singleton),即整個應用程序中只有一個實例。但如果需要,Spring允許我們?yōu)?code>@Service Bean設置其他作用域。
常見的作用域:
4.1.1 singleton(單例,默認作用域)
- 默認作用域:當你使用
@Service時,如果不指定作用域,Spring會默認使用singleton作用域。 - 單例模式:表示Spring容器中每個Bean在整個應用中只有一個實例。無論你在應用的哪個部分引用這個Bean,Spring都會返回同一個實例。
- 優(yōu)點:單例模式節(jié)省內存和提高性能,因為在整個應用生命周期中只有一個實例。
示例:
@Service
public class UserService {
// 默認是 singleton
}
在singleton作用域下,Spring在啟動時創(chuàng)建UserService實例,并在整個應用程序中共享同一個實例。
4.1.2 prototype(原型作用域)
- 多實例模式:每次需要這個Bean時,Spring都會創(chuàng)建一個新的實例。
- 使用場景:當你希望每次訪問時都能獲得一個新的
@Service對象實例,而不是共享一個單例。 - 注意:Spring僅負責創(chuàng)建新實例,不管理Bean的生命周期(如銷毀)。因此,使用
prototype時,Bean的銷毀需要手動處理。
示例:
@Service
@Scope("prototype")
public class ReportService {
// 每次注入時都會創(chuàng)建一個新實例
}
在prototype作用域下,每次請求ReportService時,Spring都會創(chuàng)建一個新的實例。
4.1.3 request(僅適用于Web應用)
- 請求作用域:表示每次HTTP請求都會創(chuàng)建一個新的Bean實例。這個作用域僅在Web應用中使用,常用于處理與單個HTTP請求相關的業(yè)務邏輯。
- 使用場景:當你希望每個HTTP請求都有一個獨立的
@Service實例時使用。
因為只有Web應用(Web應用通常是基于HTTP協(xié)議運行的應用,比如使用Spring MVC的Web應用或Spring Boot中的Web應用。)才能處理HTTP請求,并與請求和響應交互。
所以,這種針對HTTP請求的作用域才僅適用于Web應用。
示例:
@Service
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class RequestScopedService {
// 每次HTTP請求都會創(chuàng)建一個新的實例
}
value屬性指定了Bean的具體作用域類型。
WebApplicationContext.SCOPE_REQUEST:這是一個Spring提供的常量,用來表示request作用域。
你也可以直接寫成字符串"request",效果是一樣的:@Scope("request");
4.1.4 session(僅適用于Web應用):
- 會話作用域:在一個HTTP會話(
HttpSession)內共享同一個Bean實例。當會話結束時,Bean也會銷毀。 - 使用場景:當你希望一個用戶的會話中始終共享同一個
@Service實例時使用。
示例:
@Service
@Scope(value = WebApplicationContext.SCOPE_SESSION)
public class SessionScopedService {
// 在一個會話中,實例是共享的
}
4.1.5 總結
| 作用域類型 | 適用范圍 | 實例化頻率 | 生命周期 | 適用場景 |
|---|---|---|---|---|
singleton | 所有應用 | 容器啟動時創(chuàng)建一個實例 | 全局共享 | 默認作用域,適用于大部分情況 |
prototype | 所有應用 | 每次請求時創(chuàng)建新實例 | 由容器外管理 | 適合需要每次調用時生成新對象的場景 |
request | Web應用 | 每次HTTP請求時創(chuàng)建新實例 | HTTP請求范圍內 | 適用于每個HTTP請求需要獨立狀態(tài)的場景 |
session | Web應用 | 每個HTTP會話創(chuàng)建新實例 | HTTP會話范圍內 | 適用于用戶登錄會話、購物車等需要在會話內共享的場景 |
- singleton:當你需要全局共享的Bean,或者Bean是無狀態(tài)的,適合使用singleton,這是Spring的默認作用域。
- prototype:當你希望每次請求時都創(chuàng)建新的Bean實例,且這個Bean的狀態(tài)每次都不同,適合使用prototype,如處理大量獨立任務時。
- request:在Web應用中,適合處理與每個HTTP請求相關的Bean,比如每個請求都有獨立的表單驗證或請求參數(shù)處理。
- session:在Web應用中,適合管理與用戶會話相關的Bean,比如購物車、用戶登錄狀態(tài)。
4.2 生命周期(Lifecycle)
@Service Bean的生命周期受Spring容器管理,它的生命周期包括創(chuàng)建、初始化、使用和銷毀幾個階段。具體的生命周期步驟如下:
創(chuàng)建(Bean的實例化):
- 當Spring容器啟動時,它會掃描所有帶有
@Service注解的類,并為每個類創(chuàng)建一個實例(singleton作用域下)。這個過程稱為實例化,即Spring在內存中為這個類分配空間并創(chuàng)建它的對象。
- 當Spring容器啟動時,它會掃描所有帶有
初始化(Bean的初始化):
- 當Bean被實例化后,Spring會調用它的初始化方法(如果有),如
@PostConstruct。初始化方法用于配置Bean的初始狀態(tài)。
示例:
@Service public class UserService { @PostConstruct public void init() { // 初始化邏輯 System.out.println("UserService初始化"); } }在這個例子中,當
UserService類被實例化后,init()方法會被調用來完成初始化操作。- 當Bean被實例化后,Spring會調用它的初始化方法(如果有),如
使用(Bean的使用):
- 初始化完成后,Bean會被注入到需要使用它的類中。例如,
@Autowired注解會在依賴注入時將Bean注入到控制器或其他服務中,供業(yè)務邏輯使用。 - 在使用過程中,Bean的實例會參與業(yè)務邏輯的處理,并與其他組件協(xié)作。
- 初始化完成后,Bean會被注入到需要使用它的類中。例如,
銷毀(Bean的銷毀):
- 當Spring容器關閉時,Spring會調用Bean的銷毀方法(如果有),如
@PreDestroy。 - 這種銷毀通常只適用于
singleton作用域的Bean,因為prototype作用域的Bean銷毀需要手動處理。
示例:
@Service public class UserService { @PreDestroy public void destroy() { // 銷毀邏輯 System.out.println("UserService銷毀"); } }在這個例子中,當Spring容器關閉時,
destroy()方法會被調用,執(zhí)行資源釋放或清理操作。- 當Spring容器關閉時,Spring會調用Bean的銷毀方法(如果有),如
4.3 Bean生命周期的詳細流程
以@Service為例,它的完整生命周期流程如下:
- Spring掃描并發(fā)現(xiàn)
@ServiceBean。 - 實例化Bean:Spring使用默認構造函數(shù)(或其他指定構造函數(shù))創(chuàng)建該類的實例。
- 依賴注入:通過
@Autowired將該類的依賴注入(如Repository或其他@Service類)。 - 初始化Bean:Spring調用Bean的初始化方法(如
@PostConstruct)。 - Bean的使用:Bean被注入到控制器或其他類中,并執(zhí)行相應的業(yè)務邏輯。
- 銷毀Bean:當Spring容器關閉時,Spring會調用Bean的銷毀方法(如
@PreDestroy),完成清理工作。
4.4 設置自定義的作用域和生命周期管理
通過結合@Scope和生命周期回調(如@PostConstruct、@PreDestroy),你可以對@Service Bean的作用域和生命周期進行細粒度控制。
@PostConstruct 和 @PreDestroy 注解
@PostConstruct:這是一個JDK提供的注解,定義在javax.annotation包中。它用來標記在Bean被創(chuàng)建并且依賴注入完成后要執(zhí)行的初始化方法。Spring會在Bean實例化之后自動調用這個方法。@PreDestroy:這是與@PostConstruct類似的注解,也來自javax.annotation包,用來標記在Bean銷毀之前需要執(zhí)行的清理方法。Spring會在容器關閉時自動調用這個方法,用于釋放資源或執(zhí)行一些關閉操作。
@Service
@Scope("prototype")
public class PrototypeService {
@PostConstruct
public void init() {
// 初始化邏輯
System.out.println("PrototypeService 初始化");
}
@PreDestroy
public void cleanup() {
// 清理邏輯
System.out.println("PrototypeService 銷毀");
}
}
在這個例子中,PrototypeService的作用域是prototype,因此每次注入都會創(chuàng)建一個新實例。init()方法在實例創(chuàng)建時被調用,而cleanup()方法不會被自動調用,因為prototype作用域的Bean需要手動管理其銷毀。
@Service:標識服務層組件,由Spring管理其生命周期。- 作用域:控制Spring如何管理Bean的實例,默認是
singleton,還可以選擇prototype、request、session等作用域。- 生命周期:Spring負責Bean的創(chuàng)建、初始化、使用和銷毀,開發(fā)者可以通過回調方法(如
@PostConstruct和@PreDestroy)在生命周期的關鍵時刻執(zhí)行自定義邏輯。
5. 自定義服務名稱
雖然默認情況下,@Service會以類名的小寫形式將類注冊為Spring容器中的Bean,但可以通過顯式指定Bean的名稱。
@Service("customUserService")
public class UserService {
// 業(yè)務邏輯
}
- 現(xiàn)在這個Service類在Spring容器中的Bean名稱為
customUserService,可以通過這個名稱來進行注入。
6. 與AOP(面向切面編程)的結合
此處只講解了@Service注解與AOP結合使用時的具體流程,關于AOP的詳細內容請查看AOP(面向切面編程)
6.1 AOP 與 @Service 結合:生成代理對象
AOP 的核心在于為某些特定的類或方法(例如帶有日志、事務等橫切關注點的類或方法)創(chuàng)建代理對象。代理對象是指 Spring 在運行時為目標對象(例如 UserService)生成的一個增強版本,這個版本可以在方法執(zhí)行的前后或異常時插入自定義的邏輯(切面)。
當 Spring 容器掃描到 @Service 注解的類時,會根據是否配置了 AOP 相關的切面邏輯,為這個類生成代理對象。
步驟:
- Spring 檢查是否有任何切面(Aspect)應用于
UserService這樣的@Service類。切面通常通過@Aspect注解定義,描述在哪些方法或類上插入橫切邏輯。 - 如果有匹配的切面,Spring 使用動態(tài)代理機制為
UserService生成代理對象。
生成的代理對象代替了原來的 UserService Bean,Spring 容器中保存的實際上是這個代理對象,而不是直接的 UserService 實例。
6.2 方法調用時的代理行為
當使用者調用 UserService 中的方法時,實際上是通過代理對象進行調用,而不是直接調用 UserService 實例。代理對象會攔截這個方法調用,并根據 AOP 的切面邏輯決定是否執(zhí)行切面的增強邏輯。
步驟:
- 使用者調用
UserService中的方法時,代理對象會攔截這個方法調用。 - 代理對象檢查方法是否匹配切入點(Pointcut)。如果該方法匹配切入點條件(例如
execution(* com.example.service.UserService.*(..))),則會執(zhí)行對應的通知邏輯(Advice)。 - 根據 AOP 的配置,代理對象會在方法執(zhí)行的不同階段插入增強邏輯,例如:
- 在方法執(zhí)行之前插入前置通知(
@Before)。 - 在方法執(zhí)行之后插入后置通知(
@After)。 - 如果方法拋出異常,執(zhí)行異常通知(
@AfterThrowing)。
- 在方法執(zhí)行之前插入前置通知(
6.3 織入切面邏輯
代理對象在方法調用前后,會根據切入點匹配情況自動織入相應的切面邏輯。
示例切面:
@Aspect
@Component
public class LoggingAspect {
// 定義一個切入點,匹配 UserService 中的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceMethods() {}
// 前置通知:在方法執(zhí)行之前執(zhí)行日志記錄
@Before("userServiceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("開始執(zhí)行方法: " + joinPoint.getSignature().getName());
}
// 后置通知:在方法執(zhí)行之后執(zhí)行日志記錄
@After("userServiceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法執(zhí)行結束: " + joinPoint.getSignature().getName());
}
}
步驟:
- 在代理對象攔截
UserService.registerUser()方法調用時,它首先會執(zhí)行LoggingAspect中定義的前置通知(@Before)。這里會記錄日志,表示方法的開始。 - 代理對象接著調用
UserService的實際registerUser()方法,執(zhí)行核心業(yè)務邏輯。 - 方法執(zhí)行完畢后,代理對象執(zhí)行后置通知(
@After),記錄日志,表示方法結束。 - 如果方法在執(zhí)行過程中拋出異常,代理對象還會執(zhí)行異常通知(如果有定義)。
6.4 業(yè)務邏輯方法執(zhí)行
在切面(如日志、事務等)邏輯執(zhí)行完畢后,代理對象會繼續(xù)執(zhí)行實際的業(yè)務方法。這時代理對象的行為和直接調用 UserService 沒有區(qū)別,只不過在此之前或之后已經插入了額外的橫切關注點邏輯。
6.5 方法結束后的后置邏輯
業(yè)務邏輯執(zhí)行完畢后,代理對象還會檢查是否有后續(xù)的切面邏輯要執(zhí)行。如果有定義 @After 或 @AfterReturning,它會執(zhí)行這些后置通知。
后置通知的觸發(fā):
- 方法執(zhí)行結束后,代理對象執(zhí)行后置通知(如
@After),記錄方法結束的日志或執(zhí)行其他橫切關注點邏輯。 - 如果方法拋出異常,代理對象會執(zhí)行
@AfterThrowing通知,進行異常處理或日志記錄。
6.6 @Service 與 AOP 的結合流程
- Spring 容器掃描:Spring 容器掃描
@Service注解的類,并將其注冊為 Bean。 - 代理對象生成:如果有匹配的 AOP 切面,Spring 會為
UserService生成代理對象。 - 方法調用攔截:當使用者調用
UserService的方法時,代理對象攔截方法調用。 - 織入切面邏輯:代理對象根據切入點匹配情況,在方法執(zhí)行前后或拋出異常時,織入切面邏輯(如日志、事務、權限校驗等)。
- 業(yè)務邏輯執(zhí)行:代理對象執(zhí)行
UserService的實際業(yè)務邏輯。 - 后置邏輯執(zhí)行:方法執(zhí)行完畢后,代理對象繼續(xù)執(zhí)行后置通知或異常處理邏輯。
到此這篇關于Spring Service中的@Service注解的使用小結的文章就介紹到這了,更多相關Spring @Service注解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringMVC中解決@ResponseBody注解返回中文亂碼問題
這篇文章主要介紹了SpringMVC中解決@ResponseBody注解返回中文亂碼問題, 小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04
詳解MybatisPlus集成nacos導致druid連接不上數(shù)據庫
這篇文章主要介紹了詳解MybatisPlus集成nacos導致druid連接不上數(shù)據庫,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
淺談Java中SimpleDateFormat 多線程不安全原因
SimpleDateFormat是Java中用于日期時間格式化的一個類,本文主要介紹了淺談Java中SimpleDateFormat 多線程不安全原因,感興趣的可以了解一下2024-01-01
SpringBoot的API文檔生成工具SpringDoc使用詳解
這篇文章主要為大家介紹了SpringBoot的API文檔生成工具SpringDoc使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
基于SpringBoot+Redis實現(xiàn)分布式鎖
本文主要介紹了基于SpringBoot+Redis實現(xiàn)分布式鎖,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-05-05
解決異常:Invalid?keystore?format,springboot配置ssl證書格式不合法問題
這篇文章主要介紹了解決異常:Invalid?keystore?format,springboot配置ssl證書格式不合法問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

