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