Spring?Service中的@Service注解的使用小結(jié)
@Service
注解是Spring框架中用于標識業(yè)務邏輯層(Service層)的注解。它是Spring組件掃描機制的一部分,表明這個類包含業(yè)務邏輯,并且應該由Spring容器管理為一個Spring Bean。它與@Component
類似,都是標識一個類為Spring管理的Bean,但@Service
通常用于專門標識業(yè)務邏輯類。
1. @Service的基本功能
@Service
是一個特殊的@Component
,它本質(zhì)上是@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結(jié)合使用
@Autowired
注解用于將Spring容器中的Bean自動注入到其他類的字段、構(gòu)造器或方法中。@Autowired
可以用于控制器、服務層或其他任何需要依賴注入的地方。
代理對象的獲取是通過 Spring 的依賴注入機制實現(xiàn)的。你在使用的業(yè)務類(如 UserService)被 Spring 掃描到并管理為 Bean 后,Spring 會自動為它生成代理對象,并將該代理對象注入到你需要的地方。
這里依賴注入的是代理對象。
2.1常見的@Autowired用法
使用場景:
@Autowired
一般用于注入其他 Spring 容器管理的 Bean,適用于以下場景:
- 服務類之間的依賴:當一個服務類依賴于另一個服務類時。
- 控制器類依賴服務類:在 Web 應用中,控制器通常需要調(diào)用服務類。
- 服務類依賴數(shù)據(jù)訪問層:使用
@Autowired
將數(shù)據(jù)訪問層(例如Repository
類)注入到服務類中。
2.1.1構(gòu)造器注入(推薦方式)
使用構(gòu)造器注入能確保依賴在類實例化時就被正確注入,并且方便進行單元測試。
@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ù)前。
當你在構(gòu)造函數(shù)的參數(shù)中將經(jīng)過@Service注解的類的對象作為參數(shù),spring容器會自動幫你創(chuàng)建這個類(UserService)的實例,然后把這個創(chuàng)建好的實例對象引用給該構(gòu)造函數(shù)所攜帶的參數(shù) userService,相當于就是自動進行了UserService userService =new UserService();
這里的依賴注入更精確的說,是對構(gòu)造函數(shù)的參數(shù)進行依賴注入。
然后現(xiàn)在userService就是被創(chuàng)建好了的對象,然后再將這個對象的值賦值給這個類的成員變量private final UserService userService(這里的this.userService就是指這個類內(nèi)部的變量private final UserService userService中的userService,為什么要這樣做呢?因為你當時依賴注入的對象是構(gòu)造函數(shù)參數(shù)中的對象,就會導致它是作為局部變量,一旦構(gòu)造函數(shù)執(zhí)行完畢,這些局部變量就會被釋放,所以你需要有一個地方來存儲這個實例(保存到類的成員變量中),以便在類的其他方法中使用它。這就是為什么要在@Autowired注解依賴注入之前先定義private final UserService userService這個成員變量。
2.1.2字段注入
使用@Autowired
直接注入到類的成員變量中。這是最常見但不推薦的方式,因為它使得依賴關(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)建的成員變量進行依賴注入,相當于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方法之前。
這種方式其實跟構(gòu)造器注入很相似,@Autowired注解都是用在函數(shù)之前,依賴注入都是對方法中的參數(shù)進行依賴注入。
只不過唯一的區(qū)別就是構(gòu)造器注入是在你創(chuàng)建對象的時候會自動對成員變量userService進行賦值,而這中方式則是在你調(diào)用userService的setter方法時才會對userService進行賦值。
所以這種依賴注入方式一般不用。
2.1.4 不需要@Autowired注解的情況
1. 構(gòu)造函數(shù)注入
從Spring 4.3開始,如果一個Bean只有一個構(gòu)造函數(shù),Spring會自動使用該構(gòu)造函數(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框架中的泛型類,用于構(gòu)建HTTP響應。它表示一個封裝了HTTP響應的實體,包含了HTTP狀態(tài)碼、響應頭、以及響應體。
泛型參數(shù)
<User>
指的是響應體的類型。在這個例子中,<User>
表示HTTP響應體中會返回一個User
類型的對象。ResponseEntity
的主要功能是可以更靈活地控制HTTP響應:- 狀態(tài)碼:你可以使用
ResponseEntity
指定HTTP狀態(tài)碼,比如200(OK)、404(Not Found)等。 - 響應頭:你可以添加自定義的響應頭。
- 響應體:響應的內(nèi)容可以是任何類型,在這個例子中是
User
類型的對象。
- 狀態(tài)碼:你可以使用
ResponseEntity
的構(gòu)造方法和常用方法:
ResponseEntity.ok(T body)
:返回200狀態(tài)碼,響應體是傳入的對象。ResponseEntity.status(HttpStatus status)
:自定義狀態(tài)碼,結(jié)合.body(T body)
可以設置響應體。ResponseEntity.notFound()
:返回404狀態(tài)碼。ResponseEntity.noContent()
:返回204狀態(tài)碼,不帶響應體。
2.2 @Service與@Autowired結(jié)合的典型場景
場景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
注解進行構(gòu)造器注入,確保UserController
可以調(diào)用UserService
中的業(yè)務邏輯方法。
場景2:服務層之間相互調(diào)用
在復雜的業(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
,通過構(gòu)造器注入的方式,將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結(jié)合@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. 與事務管理的結(jié)合
在Spring框架中,@Service
注解與事務管理的結(jié)合是業(yè)務邏輯層非常重要的功能。事務管理保證了在處理多步驟的業(yè)務操作時,數(shù)據(jù)的一致性和完整性。例如,在處理銀行轉(zhuǎn)賬等業(yè)務時,如果其中的一個步驟失敗,整個事務應該回滾,以保證系統(tǒng)中的數(shù)據(jù)狀態(tài)正確。Spring通過@Transactional
注解結(jié)合@Service
,為開發(fā)者提供了簡潔而強大的事務管理能力。
3.1 @Transactional注解的作用
@Transactional
是Spring用于聲明式事務管理的核心注解。它可以用于類或方法上,指示Spring應該為該類或方法的操作啟用事務。事務管理保證了業(yè)務邏輯中的多個操作要么全部成功,要么全部失敗,這樣可以保證數(shù)據(jù)的一致性。
- 當某個方法被標記為
@Transactional
時,Spring會將該方法及其中涉及的數(shù)據(jù)庫操作(例如插入、更新、刪除)放在一個事務中。 - 如果方法執(zhí)行過程中出現(xiàn)異常,Spring會自動回滾事務,保證數(shù)據(jù)庫不會被部分更新。
- 如果方法執(zhí)行成功,事務將提交,數(shù)據(jù)庫中的更改將永久生效。
3.2 Spring AOP(面向切面編程)與事務管理
Spring的事務管理機制是通過AOP(面向切面編程)來實現(xiàn)的。以下是事務管理的基本流程:
- AOP代理對象:當Spring啟動時,它會通過AOP為標記了
@Transactional
的方法或類生成代理對象。這些代理對象會攔截對方法的調(diào)用。 - 事務開始:當代理對象檢測到對標記了
@Transactional
的方法的調(diào)用時,它會在方法執(zhí)行前開啟一個事務。 - 方法執(zhí)行:方法執(zhí)行過程中,Spring會暫時保存所有對數(shù)據(jù)庫的操作,并等待方法的最終結(jié)果來決定是否提交或回滾。
- 提交或回滾:如果方法執(zhí)行成功(沒有拋出異常),Spring會提交事務;如果方法執(zhí)行過程中拋出異常,則回滾事務。
3.3 @Transactional的不同應用方式
@Transactional
可以作用于類級別或方法級別,它們的行為略有不同。
3.3.1 類級別的@Transactional
如果在類上應用@Transactional
,則該類中的所有公共方法都將自動包含事務管理。每次調(diào)用該類的公共方法時,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
類中的所有公共方法都會被事務管理。當方法被調(diào)用時,事務將自動開啟;如果方法執(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的結(jié)合
通過將@Transactional
與@Service
結(jié)合使用,Spring能夠自動管理業(yè)務邏輯中的事務。常見的場景是,當業(yè)務邏輯涉及多個數(shù)據(jù)庫操作(如插入、更新、刪除)時,如果某個操作失敗,事務可以回滾,從而保證數(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); } }
代碼詳細解釋
@Transactional
注解:當調(diào)用transferMoney
方法時,Spring框架會利用@Transactional
注解為該方法開啟一個事務。
- 事務的開始:Spring使用AOP(面向切面編程)技術(shù),在
transferMoney
方法調(diào)用時攔截并啟動事務。 - 作用:確保該方法內(nèi)的所有操作作為一個整體,要么全部成功,要么全部失敗。如果方法內(nèi)部任何地方發(fā)生異?;蝈e誤,Spring將回滾事務,撤銷已經(jīng)執(zhí)行的操作。
- 目標:保護數(shù)據(jù)一致性,避免銀行轉(zhuǎn)賬這種操作中一部分成功、一部分失敗的現(xiàn)象
從數(shù)據(jù)庫中獲取付款方賬戶:
accountRepository.findById(fromAccountId)
:從數(shù)據(jù)庫中查找ID為fromAccountId
的賬戶(付款方)。orElseThrow()
:如果找不到該賬戶,則拋出異常,事務會因為異常而回滾。
扣除余額:
fromAccount.setBalance(fromAccount.getBalance() - amount)
:從賬戶的余額中扣除amount
,模擬銀行轉(zhuǎn)賬中的付款操作。
保存更新后的付款方賬戶:
accountRepository.save(fromAccount)
:將修改后的fromAccount
保存回數(shù)據(jù)庫,但此時實際的數(shù)據(jù)庫操作并沒有立即持久化,數(shù)據(jù)仍在事務中,等待事務提交。- 注意:在事務提交前,雖然代碼已經(jīng)調(diào)用
save
方法,但對數(shù)據(jù)庫的實際寫操作還沒有發(fā)生。
事務提交
- 如果
transferMoney
方法執(zhí)行到最后一步,沒有拋出異常,則Spring會自動提交事務,將所有的數(shù)據(jù)庫操作(付款方賬戶的扣款和收款方賬戶的存款)一并生效。 - 提交事務時,
accountRepository.save(fromAccount)
和accountRepository.save(toAccount)
中的數(shù)據(jù)庫更改才會被實際寫入到數(shù)據(jù)庫中。
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ù)據(jù)庫操作之間的隔離程度,防止臟讀、不可重復讀和幻讀等問題。
常見的隔離級別:
READ_UNCOMMITTED
:最低的隔離級別,可能會出現(xiàn)臟讀。READ_COMMITTED
:保證讀取的數(shù)據(jù)是已提交的,防止臟讀。REPEATABLE_READ
:同一個事務中多次讀取相同的數(shù)據(jù)結(jié)果相同,防止不可重復讀。SERIALIZABLE
:最高的隔離級別,防止幻讀。
示例:
@Transactional(isolation = Isolation.READ_COMMITTED) public void updateAccount(Account account) { accountRepository.save(account); }
3.5.3 timeout(超時)
定義事務的超時時間,如果事務在指定的時間內(nèi)沒有完成,將會回滾。
示例:
@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的常見使用場景
- 銀行交易:轉(zhuǎn)賬、提款等涉及多個數(shù)據(jù)庫操作的業(yè)務,如果其中一步失敗,整個交易應該回滾。
- 訂單處理:在電商系統(tǒng)中,訂單的創(chuàng)建、庫存的減少、支付的處理等步驟應該作為一個整體事務來執(zhí)行。
- 批量更新:批量插入、更新、刪除數(shù)據(jù)的操作需要在一個事務中執(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é)省內(nèi)存和提高性能,因為在整個應用生命周期中只有一個實例。
示例:
@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請求相關(guān)的業(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
)內(nèi)共享同一個Bean實例。當會話結(jié)束時,Bean也會銷毀。 - 使用場景:當你希望一個用戶的會話中始終共享同一個
@Service
實例時使用。
示例:
@Service @Scope(value = WebApplicationContext.SCOPE_SESSION) public class SessionScopedService { // 在一個會話中,實例是共享的 }
4.1.5 總結(jié)
作用域類型 | 適用范圍 | 實例化頻率 | 生命周期 | 適用場景 |
---|---|---|---|---|
singleton | 所有應用 | 容器啟動時創(chuàng)建一個實例 | 全局共享 | 默認作用域,適用于大部分情況 |
prototype | 所有應用 | 每次請求時創(chuàng)建新實例 | 由容器外管理 | 適合需要每次調(diào)用時生成新對象的場景 |
request | Web應用 | 每次HTTP請求時創(chuàng)建新實例 | HTTP請求范圍內(nèi) | 適用于每個HTTP請求需要獨立狀態(tài)的場景 |
session | Web應用 | 每個HTTP會話創(chuàng)建新實例 | HTTP會話范圍內(nèi) | 適用于用戶登錄會話、購物車等需要在會話內(nèi)共享的場景 |
- singleton:當你需要全局共享的Bean,或者Bean是無狀態(tài)的,適合使用singleton,這是Spring的默認作用域。
- prototype:當你希望每次請求時都創(chuàng)建新的Bean實例,且這個Bean的狀態(tài)每次都不同,適合使用prototype,如處理大量獨立任務時。
- request:在Web應用中,適合處理與每個HTTP請求相關(guān)的Bean,比如每個請求都有獨立的表單驗證或請求參數(shù)處理。
- session:在Web應用中,適合管理與用戶會話相關(guān)的Bean,比如購物車、用戶登錄狀態(tài)。
4.2 生命周期(Lifecycle)
@Service
Bean的生命周期受Spring容器管理,它的生命周期包括創(chuàng)建、初始化、使用和銷毀幾個階段。具體的生命周期步驟如下:
創(chuàng)建(Bean的實例化):
- 當Spring容器啟動時,它會掃描所有帶有
@Service
注解的類,并為每個類創(chuàng)建一個實例(singleton
作用域下)。這個過程稱為實例化,即Spring在內(nèi)存中為這個類分配空間并創(chuàng)建它的對象。
- 當Spring容器啟動時,它會掃描所有帶有
初始化(Bean的初始化):
- 當Bean被實例化后,Spring會調(diào)用它的初始化方法(如果有),如
@PostConstruct
。初始化方法用于配置Bean的初始狀態(tài)。
示例:
@Service public class UserService { @PostConstruct public void init() { // 初始化邏輯 System.out.println("UserService初始化"); } }
在這個例子中,當
UserService
類被實例化后,init()
方法會被調(diào)用來完成初始化操作。- 當Bean被實例化后,Spring會調(diào)用它的初始化方法(如果有),如
使用(Bean的使用):
- 初始化完成后,Bean會被注入到需要使用它的類中。例如,
@Autowired
注解會在依賴注入時將Bean注入到控制器或其他服務中,供業(yè)務邏輯使用。 - 在使用過程中,Bean的實例會參與業(yè)務邏輯的處理,并與其他組件協(xié)作。
- 初始化完成后,Bean會被注入到需要使用它的類中。例如,
銷毀(Bean的銷毀):
- 當Spring容器關(guān)閉時,Spring會調(diào)用Bean的銷毀方法(如果有),如
@PreDestroy
。 - 這種銷毀通常只適用于
singleton
作用域的Bean,因為prototype
作用域的Bean銷毀需要手動處理。
示例:
@Service public class UserService { @PreDestroy public void destroy() { // 銷毀邏輯 System.out.println("UserService銷毀"); } }
在這個例子中,當Spring容器關(guān)閉時,
destroy()
方法會被調(diào)用,執(zhí)行資源釋放或清理操作。- 當Spring容器關(guān)閉時,Spring會調(diào)用Bean的銷毀方法(如果有),如
4.3 Bean生命周期的詳細流程
以@Service
為例,它的完整生命周期流程如下:
- Spring掃描并發(fā)現(xiàn)
@Service
Bean。 - 實例化Bean:Spring使用默認構(gòu)造函數(shù)(或其他指定構(gòu)造函數(shù))創(chuàng)建該類的實例。
- 依賴注入:通過
@Autowired
將該類的依賴注入(如Repository
或其他@Service
類)。 - 初始化Bean:Spring調(diào)用Bean的初始化方法(如
@PostConstruct
)。 - Bean的使用:Bean被注入到控制器或其他類中,并執(zhí)行相應的業(yè)務邏輯。
- 銷毀Bean:當Spring容器關(guān)閉時,Spring會調(diào)用Bean的銷毀方法(如
@PreDestroy
),完成清理工作。
4.4 設置自定義的作用域和生命周期管理
通過結(jié)合@Scope
和生命周期回調(diào)(如@PostConstruct
、@PreDestroy
),你可以對@Service
Bean的作用域和生命周期進行細粒度控制。
@PostConstruct
和 @PreDestroy
注解
@PostConstruct
:這是一個JDK提供的注解,定義在javax.annotation
包中。它用來標記在Bean被創(chuàng)建并且依賴注入完成后要執(zhí)行的初始化方法。Spring會在Bean實例化之后自動調(diào)用這個方法。@PreDestroy
:這是與@PostConstruct
類似的注解,也來自javax.annotation
包,用來標記在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)建一個新實例。init()
方法在實例創(chuàng)建時被調(diào)用,而cleanup()
方法不會被自動調(diào)用,因為prototype
作用域的Bean需要手動管理其銷毀。
@Service
:標識服務層組件,由Spring管理其生命周期。- 作用域:控制Spring如何管理Bean的實例,默認是
singleton
,還可以選擇prototype
、request
、session
等作用域。- 生命周期:Spring負責Bean的創(chuàng)建、初始化、使用和銷毀,開發(fā)者可以通過回調(diào)方法(如
@PostConstruct
和@PreDestroy
)在生命周期的關(guān)鍵時刻執(zhí)行自定義邏輯。
5. 自定義服務名稱
雖然默認情況下,@Service
會以類名的小寫形式將類注冊為Spring容器中的Bean,但可以通過顯式指定Bean的名稱。
@Service("customUserService") public class UserService { // 業(yè)務邏輯 }
- 現(xiàn)在這個Service類在Spring容器中的Bean名稱為
customUserService
,可以通過這個名稱來進行注入。
6. 與AOP(面向切面編程)的結(jié)合
此處只講解了@Service注解與AOP結(jié)合使用時的具體流程,關(guān)于AOP的詳細內(nèi)容請查看AOP(面向切面編程)
6.1 AOP 與 @Service 結(jié)合:生成代理對象
AOP 的核心在于為某些特定的類或方法(例如帶有日志、事務等橫切關(guān)注點的類或方法)創(chuàng)建代理對象。代理對象是指 Spring 在運行時為目標對象(例如 UserService
)生成的一個增強版本,這個版本可以在方法執(zhí)行的前后或異常時插入自定義的邏輯(切面)。
當 Spring 容器掃描到 @Service
注解的類時,會根據(jù)是否配置了 AOP 相關(guān)的切面邏輯,為這個類生成代理對象。
步驟:
- Spring 檢查是否有任何切面(Aspect)應用于
UserService
這樣的@Service
類。切面通常通過@Aspect
注解定義,描述在哪些方法或類上插入橫切邏輯。 - 如果有匹配的切面,Spring 使用動態(tài)代理機制為
UserService
生成代理對象。
生成的代理對象代替了原來的 UserService
Bean,Spring 容器中保存的實際上是這個代理對象,而不是直接的 UserService
實例。
6.2 方法調(diào)用時的代理行為
當使用者調(diào)用 UserService
中的方法時,實際上是通過代理對象進行調(diào)用,而不是直接調(diào)用 UserService
實例。代理對象會攔截這個方法調(diào)用,并根據(jù) AOP 的切面邏輯決定是否執(zhí)行切面的增強邏輯。
步驟:
- 使用者調(diào)用
UserService
中的方法時,代理對象會攔截這個方法調(diào)用。 - 代理對象檢查方法是否匹配切入點(Pointcut)。如果該方法匹配切入點條件(例如
execution(* com.example.service.UserService.*(..))
),則會執(zhí)行對應的通知邏輯(Advice)。 - 根據(jù) AOP 的配置,代理對象會在方法執(zhí)行的不同階段插入增強邏輯,例如:
- 在方法執(zhí)行之前插入前置通知(
@Before
)。 - 在方法執(zhí)行之后插入后置通知(
@After
)。 - 如果方法拋出異常,執(zhí)行異常通知(
@AfterThrowing
)。
- 在方法執(zhí)行之前插入前置通知(
6.3 織入切面邏輯
代理對象在方法調(diào)用前后,會根據(jù)切入點匹配情況自動織入相應的切面邏輯。
示例切面:
@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í)行結(jié)束: " + joinPoint.getSignature().getName()); } }
步驟:
- 在代理對象攔截
UserService.registerUser()
方法調(diào)用時,它首先會執(zhí)行LoggingAspect
中定義的前置通知(@Before
)。這里會記錄日志,表示方法的開始。 - 代理對象接著調(diào)用
UserService
的實際registerUser()
方法,執(zhí)行核心業(yè)務邏輯。 - 方法執(zhí)行完畢后,代理對象執(zhí)行后置通知(
@After
),記錄日志,表示方法結(jié)束。 - 如果方法在執(zhí)行過程中拋出異常,代理對象還會執(zhí)行異常通知(如果有定義)。
6.4 業(yè)務邏輯方法執(zhí)行
在切面(如日志、事務等)邏輯執(zhí)行完畢后,代理對象會繼續(xù)執(zhí)行實際的業(yè)務方法。這時代理對象的行為和直接調(diào)用 UserService
沒有區(qū)別,只不過在此之前或之后已經(jīng)插入了額外的橫切關(guān)注點邏輯。
6.5 方法結(jié)束后的后置邏輯
業(yè)務邏輯執(zhí)行完畢后,代理對象還會檢查是否有后續(xù)的切面邏輯要執(zhí)行。如果有定義 @After
或 @AfterReturning
,它會執(zhí)行這些后置通知。
后置通知的觸發(fā):
- 方法執(zhí)行結(jié)束后,代理對象執(zhí)行后置通知(如
@After
),記錄方法結(jié)束的日志或執(zhí)行其他橫切關(guān)注點邏輯。 - 如果方法拋出異常,代理對象會執(zhí)行
@AfterThrowing
通知,進行異常處理或日志記錄。
6.6 @Service 與 AOP 的結(jié)合流程
- Spring 容器掃描:Spring 容器掃描
@Service
注解的類,并將其注冊為 Bean。 - 代理對象生成:如果有匹配的 AOP 切面,Spring 會為
UserService
生成代理對象。 - 方法調(diào)用攔截:當使用者調(diào)用
UserService
的方法時,代理對象攔截方法調(diào)用。 - 織入切面邏輯:代理對象根據(jù)切入點匹配情況,在方法執(zhí)行前后或拋出異常時,織入切面邏輯(如日志、事務、權(quán)限校驗等)。
- 業(yè)務邏輯執(zhí)行:代理對象執(zhí)行
UserService
的實際業(yè)務邏輯。 - 后置邏輯執(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導致druid連接不上數(shù)據(jù)庫
這篇文章主要介紹了詳解MybatisPlus集成nacos導致druid連接不上數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11淺談Java中SimpleDateFormat 多線程不安全原因
SimpleDateFormat是Java中用于日期時間格式化的一個類,本文主要介紹了淺談Java中SimpleDateFormat 多線程不安全原因,感興趣的可以了解一下2024-01-01SpringBoot的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