Spring Service中的@Service注解的使用
@Service
注解是Spring框架中用于标识业务逻辑层(Service层)的注解。它是Spring组件扫描机制的一部分,表明这个类包含业务逻辑,并且应该由Spring容器管理为一个Spring Bean。它与@Component
类似,都是标识一个类为Spring管理的Bean,但@Service
通常用于专门标识业务逻辑类。
1. @Service
的基本功能
@Service
是一个特殊的@Component
,它本质上是@Component
的派生注解。通过使用@Service
,我们可以告诉Spring容器去自动扫描和注册这些类为Bean,供依赖注入使用。
@Service
public class UserService {public User getUserById(Long id) {// 业务逻辑代码return new User(id, "John Doe");}
}
在这个例子中,UserService
类被@Service
注解标识,Spring会将它作为Bean注册到应用上下文中。
2. 如何与@Autowired
结合使用
@Autowired
注解用于将Spring容器中的Bean自动注入到其他类的字段、构造器或方法中。@Autowired
可以用于控制器、服务层或其他任何需要依赖注入的地方。
代理对象的获取是通过 Spring 的依赖注入机制实现的。你在使用的业务类(如
UserService
)被 Spring 扫描到并管理为 Bean 后,Spring 会自动为它生成代理对象,并将该代理对象注入到你需要的地方。这里依赖注入的是代理对象。
2.1常见的@Autowired
用法
2.1.1构造器注入(推荐方式)
使用构造器注入能确保依赖在类实例化时就被正确注入,并且方便进行单元测试。
@RestController
public class UserController {private final UserService userService;@Autowiredpublic 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注解使用在构造函数前。
当你在构造函数的参数中将经过@Service注解的类的对象作为参数,spring容器会自动帮你创建这个类(UserService)的实例,然后把这个创建好的实例对象引用给该构造函数所携带的参数 userService,相当于就是自动进行了UserService userService =new UserService();
这里的依赖注入更精确的说,是对构造函数的参数进行依赖注入。
然后现在userService就是被创建好了的对象,然后再将这个对象的值赋值给这个类的成员变量private final UserService userService(这里的this.userService就是指这个类内部的变量private final UserService userService中的userService,为什么要这样做呢?因为你当时依赖注入的对象是构造函数参数中的对象,就会导致它是作为局部变量,一旦构造函数执行完毕,这些局部变量就会被释放,所以你需要有一个地方来存储这个实例(保存到类的成员变量中),以便在类的其他方法中使用它。这就是为什么要在@Autowired注解依赖注入之前先定义private final UserService userService这个成员变量。
2.1.2字段注入
使用@Autowired
直接注入到类的成员变量中。这是最常见但不推荐的方式,因为它使得依赖关系不那么显式,并且在单元测试中可能不太灵活。
-
@RestController public class UserController {@Autowiredprivate UserService userService;@GetMapping("/users/{id}")public ResponseEntity<User> getUserById(@PathVariable Long id) {User user = userService.getUserById(id);return ResponseEntity.ok(user);} }
@Autowired注解使用在类的成员变量之前。
直接对你所创建的成员变量进行依赖注入,相当于private UserService userService = new UserService();
2.1.3Setter注入
通过提供一个setter方法来注入依赖,尽管使用频率较低,但它可以在某些需要动态设置依赖的场景中使用。
@RestController
public class UserController {private UserService userService;@Autowiredpublic 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注解都是用在函数之前,依赖注入都是对方法中的参数进行依赖注入。
只不过唯一的区别就是构造器注入是在你创建对象的时候会自动对成员变量userService进行赋值,而这中方式则是在你调用userService的setter方法时才会对userService进行赋值。
所以这种依赖注入方式一般不用。
ResponseEntity<User>
是什么类型?
ResponseEntity<User>
是一个Spring框架中的泛型类,用于构建HTTP响应。它表示一个封装了HTTP响应的实体,包含了HTTP状态码、响应头、以及响应体。
-
泛型参数
<User>
指的是响应体的类型。在这个例子中,<User>
表示HTTP响应体中会返回一个User
类型的对象。 -
ResponseEntity
的主要功能是可以更灵活地控制HTTP响应:- 状态码:你可以使用
ResponseEntity
指定HTTP状态码,比如200(OK)、404(Not Found)等。 - 响应头:你可以添加自定义的响应头。
- 响应体:响应的内容可以是任何类型,在这个例子中是
User
类型的对象。
- 状态码:你可以使用
ResponseEntity
的构造方法和常用方法:
ResponseEntity.ok(T body)
:返回200状态码,响应体是传入的对象。ResponseEntity.status(HttpStatus status)
:自定义状态码,结合.body(T body)
可以设置响应体。ResponseEntity.notFound()
:返回404状态码。ResponseEntity.noContent()
:返回204状态码,不带响应体。
2.2 @Service
与@Autowired
结合的典型场景
场景1:控制层注入Service层
在Spring MVC的控制层(@Controller
或@RestController
)中,业务逻辑通常委托给服务层处理。这种场景下,控制层会通过@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;@Autowiredpublic 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
中的业务逻辑方法。
场景2:服务层之间相互调用
在复杂的业务场景中,一个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;@Autowiredpublic 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
区分多个Bean
在某些情况下,如果Spring容器中有多个同类型的Bean(例如多个@Service
),需要通过@Qualifier
注解来明确指定注入的具体Bean。
@Service("basicOrderService")
public class BasicOrderService implements OrderService {// 实现逻辑
}@Service("advancedOrderService")
public class AdvancedOrderService implements OrderService {// 实现逻辑
}@Service
public class PaymentService {private final OrderService orderService;@Autowiredpublic PaymentService(@Qualifier("basicOrderService") OrderService orderService) {this.orderService = orderService;}// 业务逻辑
}
- 通过
@Qualifier("basicOrderService")
,我们指定注入的具体实现类BasicOrderService
。
2.3.2结合@Primary
注解
@Primary
注解用于标识在多个相同类型的Bean中优先注入某个Bean。如果没有使用@Qualifier
指定Bean,Spring会注入@Primary
标注的Bean。
@Service
@Primary
public class BasicOrderService implements OrderService {// 实现逻辑
}@Service
public class AdvancedOrderService implements OrderService {// 实现逻辑
}
BasicOrderService
标注了@Primary
,因此在没有指定@Qualifier
的情况下,Spring会优先注入BasicOrderService
。
3. 与事务管理的结合
在Spring框架中,@Service
注解与事务管理的结合是业务逻辑层非常重要的功能。事务管理保证了在处理多步骤的业务操作时,数据的一致性和完整性。例如,在处理银行转账等业务时,如果其中的一个步骤失败,整个事务应该回滚,以保证系统中的数据状态正确。Spring通过@Transactional
注解结合@Service
,为开发者提供了简洁而强大的事务管理能力。
3.1 @Transactional
注解的作用
@Transactional
是Spring用于声明式事务管理的核心注解。它可以用于类或方法上,指示Spring应该为该类或方法的操作启用事务。事务管理保证了业务逻辑中的多个操作要么全部成功,要么全部失败,这样可以保证数据的一致性。
- 当某个方法被标记为
@Transactional
时,Spring会将该方法及其中涉及的数据库操作(例如插入、更新、删除)放在一个事务中。 - 如果方法执行过程中出现异常,Spring会自动回滚事务,保证数据库不会被部分更新。
- 如果方法执行成功,事务将提交,数据库中的更改将永久生效。
3.2 Spring AOP(面向切面编程)与事务管理
Spring的事务管理机制是通过AOP(面向切面编程)来实现的。以下是事务管理的基本流程:
- AOP代理对象:当Spring启动时,它会通过AOP为标记了
@Transactional
的方法或类生成代理对象。这些代理对象会拦截对方法的调用。 - 事务开始:当代理对象检测到对标记了
@Transactional
的方法的调用时,它会在方法执行前开启一个事务。 - 方法执行:方法执行过程中,Spring会暂时保存所有对数据库的操作,并等待方法的最终结果来决定是否提交或回滚。
- 提交或回滚:如果方法执行成功(没有抛出异常),Spring会提交事务;如果方法执行过程中抛出异常,则回滚事务。
3.3 @Transactional
的不同应用方式
@Transactional
可以作用于类级别或方法级别,它们的行为略有不同。
3.3.1 类级别的@Transactional
如果在类上应用@Transactional
,则该类中的所有公共方法都将自动包含事务管理。每次调用该类的公共方法时,Spring都会自动开启一个事务,方法执行成功时提交事务,方法执行失败时回滚事务。
@Service
@Transactional // 应用于整个类
public class OrderService {@Autowiredprivate 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
类中的所有公共方法都会被事务管理。当方法被调用时,事务将自动开启;如果方法执行失败(抛出异常),事务将回滚;如果方法执行成功,事务将提交。
3.3.2 方法级别的@Transactional
如果你不想对整个类的所有方法都使用事务管理,可以将@Transactional
仅应用于特定方法。在这种情况下,只有被标记的方法会执行事务管理。
@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Transactional // 仅应用于此方法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);}
}
在此例中,placeOrder
方法具有事务管理,而cancelOrder
方法则不会启用事务管理。
3.4 @Service
与@Transactional
的结合
通过将@Transactional
与@Service
结合使用,Spring能够自动管理业务逻辑中的事务。常见的场景是,当业务逻辑涉及多个数据库操作(如插入、更新、删除)时,如果某个操作失败,事务可以回滚,从而保证数据一致性。
示例代码:
@Service
public class BankService {@Autowiredprivate AccountRepository accountRepository;@Transactionalpublic 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
方法调用时拦截并启动事务。- 作用:确保该方法内的所有操作作为一个整体,要么全部成功,要么全部失败。如果方法内部任何地方发生异常或错误,Spring将回滚事务,撤销已经执行的操作。
- 目标:保护数据一致性,避免银行转账这种操作中一部分成功、一部分失败的现象
-
从数据库中获取付款方账户:
accountRepository.findById(fromAccountId)
:从数据库中查找ID为fromAccountId
的账户(付款方)。orElseThrow()
:如果找不到该账户,则抛出异常,事务会因为异常而回滚。
-
扣除余额:
fromAccount.setBalance(fromAccount.getBalance() - amount)
:从账户的余额中扣除amount
,模拟银行转账中的付款操作。
-
保存更新后的付款方账户:
accountRepository.save(fromAccount)
:将修改后的fromAccount
保存回数据库,但此时实际的数据库操作并没有立即持久化,数据仍在事务中,等待事务提交。- 注意:在事务提交前,虽然代码已经调用
save
方法,但对数据库的实际写操作还没有发生。
- 事务提交
- 如果
transferMoney
方法执行到最后一步,没有抛出异常,则Spring会自动提交事务,将所有的数据库操作(付款方账户的扣款和收款方账户的存款)一并生效。- 提交事务时,
accountRepository.save(fromAccount)
和accountRepository.save(toAccount)
中的数据库更改才会被实际写入到数据库中。
3.5 @Transactional
的事务属性
@Transactional
提供了多个属性,用来细化事务的管理行为。常用属性包括:
3.5.1 propagation
(传播行为)
示例:
- 定义当前事务方法是否应该运行在一个现有的事务中,或者是否应该创建一个新的事务。
- 常见的传播属性:
REQUIRED
:默认值。如果当前有事务,使用当前事务;如果没有,创建一个新事务。REQUIRES_NEW
:每次都会创建一个新事务,并暂停当前事务。SUPPORTS
:如果当前有事务,则支持当前事务;如果没有事务,也可以不使用事务。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAccount(Account account) {accountRepository.save(account);
}
3.5.2 isolation
(隔离级别)
- 定义数据库操作之间的隔离程度,防止脏读、不可重复读和幻读等问题。
- 常见的隔离级别:
READ_UNCOMMITTED
:最低的隔离级别,可能会出现脏读。READ_COMMITTED
:保证读取的数据是已提交的,防止脏读。REPEATABLE_READ
:同一个事务中多次读取相同的数据结果相同,防止不可重复读。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() {// 执行耗时任务
}
3.5.4 readOnly
(只读事务)
- 如果事务只进行查询操作而不进行更新,
readOnly = true
可以优化性能。
示例:
@Transactional(readOnly = true)
public Account getAccount(Long accountId) {return accountRepository.findById(accountId).orElseThrow();
}
3.5.5 rollbackFor
和 noRollbackFor
默认情况下,Spring的事务管理机制只会在运行时异常(
RuntimeException
)或Error发生时回滚事务。而对于受检异常(Checked Exception
),Spring不会自动回滚,除非你显式地配置rollbackFor
属性来指定。
rollbackFor
:指定哪些异常会导致事务回滚,默认情况下,只有未捕获的运行时异常会导致回滚。noRollbackFor
:指定哪些异常不会导致事务回滚。
示例:
@Transactional(rollbackFor = {Exception.class})
public void riskyOperation() throws Exception {// 执行可能抛出受检异常的操作
}
这个写法表示只要抛出了Exception
类型或它的子类异常,Spring就会回滚事务。
这里的Exception只是代指异常的类型,实际使用的时候要加入实际的异常类型,比如:SQLException
(受检异常)或DataAccessException
(运行时异常) 。
-
Exception.class
表示Exception
这个类的类对象。在Java中,每个类都有一个对应的类对象(Class
对象),它可以通过.class
语法获得。Exception.class
表示 Java 的Exception
类本身,而不是Exception
的一个实例。 -
{Exception.class}
是一个包含Exception.class
的数组,这是 Java 数组的简写形式。你可以在数组中放入多个类类型,像这样:{Exception.class, IOException.class}
,表示这是一个由多个类对象组成的数组。
3.6 @Service
与@Transactional
的常见使用场景
- 银行交易:转账、提款等涉及多个数据库操作的业务,如果其中一步失败,整个交易应该回滚。
- 订单处理:在电商系统中,订单的创建、库存的减少、支付的处理等步骤应该作为一个整体事务来执行。
- 批量更新:批量插入、更新、删除数据的操作需要在一个事务中执行,以保证操作的原子性。
4. 作用域与生命周期
4.1作用域(Scope)
作用域决定了Spring容器如何管理Bean的实例。Spring默认会为每个@Service
Bean分配一个单例作用域(singleton
),即整个应用程序中只有一个实例。但如果需要,Spring允许我们为@Service
Bean设置其他作用域。
常见的作用域:
4.1.1 singleton
(单例,默认作用域)
- 默认作用域:当你使用
@Service
时,如果不指定作用域,Spring会默认使用singleton
作用域。 - 单例模式:表示Spring容器中每个Bean在整个应用中只有一个实例。无论你在应用的哪个部分引用这个Bean,Spring都会返回同一个实例。
- 优点:单例模式节省内存和提高性能,因为在整个应用生命周期中只有一个实例。
示例:
@Service
public class UserService {// 默认是 singleton
}
在singleton
作用域下,Spring在启动时创建UserService
实例,并在整个应用程序中共享同一个实例。
4.1.2 prototype
(原型作用域)
- 多实例模式:每次需要这个Bean时,Spring都会创建一个新的实例。
- 使用场景:当你希望每次访问时都能获得一个新的
@Service
对象实例,而不是共享一个单例。 - 注意:Spring仅负责创建新实例,不管理Bean的生命周期(如销毁)。因此,使用
prototype
时,Bean的销毁需要手动处理。
示例:
@Service
@Scope("prototype")
public class ReportService {// 每次注入时都会创建一个新实例
}
在prototype
作用域下,每次请求ReportService
时,Spring都会创建一个新的实例。
4.1.3 request
(仅适用于Web应用)
- 请求作用域:表示每次HTTP请求都会创建一个新的Bean实例。这个作用域仅在Web应用中使用,常用于处理与单个HTTP请求相关的业务逻辑。
- 使用场景:当你希望每个HTTP请求都有一个独立的
@Service
实例时使用。
因为只有Web应用(Web应用通常是基于HTTP协议运行的应用,比如使用Spring MVC的Web应用或Spring Boot中的Web应用。)才能处理HTTP请求,并与请求和响应交互。
所以,这种针对HTTP请求的作用域才仅适用于Web应用。
示例:
@Service
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class RequestScopedService {// 每次HTTP请求都会创建一个新的实例
}
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 | 所有应用 | 容器启动时创建一个实例 | 全局共享 | 默认作用域,适用于大部分情况 |
prototype | 所有应用 | 每次请求时创建新实例 | 由容器外管理 | 适合需要每次调用时生成新对象的场景 |
request | Web应用 | 每次HTTP请求时创建新实例 | HTTP请求范围内 | 适用于每个HTTP请求需要独立状态的场景 |
session | Web应用 | 每个HTTP会话创建新实例 | HTTP会话范围内 | 适用于用户登录会话、购物车等需要在会话内共享的场景 |
singleton
:当你需要全局共享的Bean,或者Bean是无状态的,适合使用singleton
,这是Spring的默认作用域。prototype
:当你希望每次请求时都创建新的Bean实例,且这个Bean的状态每次都不同,适合使用prototype
,如处理大量独立任务时。request
:在Web应用中,适合处理与每个HTTP请求相关的Bean,比如每个请求都有独立的表单验证或请求参数处理。session
:在Web应用中,适合管理与用户会话相关的Bean,比如购物车、用户登录状态。
4.2 生命周期(Lifecycle)
@Service
Bean的生命周期受Spring容器管理,它的生命周期包括创建、初始化、使用和销毁几个阶段。具体的生命周期步骤如下:
-
创建(Bean的实例化):
- 当Spring容器启动时,它会扫描所有带有
@Service
注解的类,并为每个类创建一个实例(singleton
作用域下)。这个过程称为实例化,即Spring在内存中为这个类分配空间并创建它的对象。
- 当Spring容器启动时,它会扫描所有带有
-
初始化(Bean的初始化):
- 当Bean被实例化后,Spring会调用它的初始化方法(如果有),如
@PostConstruct
。初始化方法用于配置Bean的初始状态。
示例:
@Service public class UserService {@PostConstructpublic void init() {// 初始化逻辑System.out.println("UserService初始化");} }
在这个例子中,当
UserService
类被实例化后,init()
方法会被调用来完成初始化操作。 - 当Bean被实例化后,Spring会调用它的初始化方法(如果有),如
-
使用(Bean的使用):
- 初始化完成后,Bean会被注入到需要使用它的类中。例如,
@Autowired
注解会在依赖注入时将Bean注入到控制器或其他服务中,供业务逻辑使用。 - 在使用过程中,Bean的实例会参与业务逻辑的处理,并与其他组件协作。
- 初始化完成后,Bean会被注入到需要使用它的类中。例如,
-
销毁(Bean的销毁):
- 当Spring容器关闭时,Spring会调用Bean的销毁方法(如果有),如
@PreDestroy
。 - 这种销毁通常只适用于
singleton
作用域的Bean,因为prototype
作用域的Bean销毁需要手动处理。
示例:
@Service public class UserService {@PreDestroypublic void destroy() {// 销毁逻辑System.out.println("UserService销毁");} }
在这个例子中,当Spring容器关闭时,
destroy()
方法会被调用,执行资源释放或清理操作。 - 当Spring容器关闭时,Spring会调用Bean的销毁方法(如果有),如
4.3 Bean生命周期的详细流程
以@Service
为例,它的完整生命周期流程如下:
- Spring扫描并发现
@Service
Bean。 - 实例化Bean:Spring使用默认构造函数(或其他指定构造函数)创建该类的实例。
- 依赖注入:通过
@Autowired
将该类的依赖注入(如Repository
或其他@Service
类)。 - 初始化Bean:Spring调用Bean的初始化方法(如
@PostConstruct
)。 - Bean的使用:Bean被注入到控制器或其他类中,并执行相应的业务逻辑。
- 销毁Bean:当Spring容器关闭时,Spring会调用Bean的销毁方法(如
@PreDestroy
),完成清理工作。
4.4 设置自定义的作用域和生命周期管理
通过结合@Scope
和生命周期回调(如@PostConstruct
、@PreDestroy
),你可以对@Service
Bean的作用域和生命周期进行细粒度控制。
@PostConstruct
和 @PreDestroy
注解
-
@PostConstruct
:这是一个JDK提供的注解,定义在javax.annotation
包中。它用来标记在Bean被创建并且依赖注入完成后要执行的初始化方法。Spring会在Bean实例化之后自动调用这个方法。 -
@PreDestroy
:这是与@PostConstruct
类似的注解,也来自javax.annotation
包,用来标记在Bean销毁之前需要执行的清理方法。Spring会在容器关闭时自动调用这个方法,用于释放资源或执行一些关闭操作。
@Service
@Scope("prototype")
public class PrototypeService {@PostConstructpublic void init() {// 初始化逻辑System.out.println("PrototypeService 初始化");}@PreDestroypublic void cleanup() {// 清理逻辑System.out.println("PrototypeService 销毁");}
}
在这个例子中,PrototypeService
的作用域是prototype
,因此每次注入都会创建一个新实例。init()
方法在实例创建时被调用,而cleanup()
方法不会被自动调用,因为prototype
作用域的Bean需要手动管理其销毁。
@Service
:标识服务层组件,由Spring管理其生命周期。- 作用域:控制Spring如何管理Bean的实例,默认是
singleton
,还可以选择prototype
、request
、session
等作用域。- 生命周期:Spring负责Bean的创建、初始化、使用和销毁,开发者可以通过回调方法(如
@PostConstruct
和@PreDestroy
)在生命周期的关键时刻执行自定义逻辑。
5. 自定义服务名称
虽然默认情况下,@Service
会以类名的小写形式将类注册为Spring容器中的Bean,但可以通过显式指定Bean的名称。
@Service("customUserService")
public class UserService {// 业务逻辑
}
- 现在这个Service类在Spring容器中的Bean名称为
customUserService
,可以通过这个名称来进行注入。
6. 与AOP(面向切面编程)的结合
此处只讲解了@Service注解与AOP结合使用时的具体流程,关于AOP的详细内容请查看AOP(面向切面编程)
6.1 AOP 与 @Service
结合:生成代理对象
AOP 的核心在于为某些特定的类或方法(例如带有日志、事务等横切关注点的类或方法)创建代理对象。代理对象是指 Spring 在运行时为目标对象(例如 UserService
)生成的一个增强版本,这个版本可以在方法执行的前后或异常时插入自定义的逻辑(切面)。
当 Spring 容器扫描到 @Service
注解的类时,会根据是否配置了 AOP 相关的切面逻辑,为这个类生成代理对象。
步骤:
- Spring 检查是否有任何切面(Aspect)应用于
UserService
这样的@Service
类。切面通常通过@Aspect
注解定义,描述在哪些方法或类上插入横切逻辑。 - 如果有匹配的切面,Spring 使用动态代理机制为
UserService
生成代理对象。
生成的代理对象代替了原来的 UserService
Bean,Spring 容器中保存的实际上是这个代理对象,而不是直接的 UserService
实例。
6.2 方法调用时的代理行为
当使用者调用 UserService
中的方法时,实际上是通过代理对象进行调用,而不是直接调用 UserService
实例。代理对象会拦截这个方法调用,并根据 AOP 的切面逻辑决定是否执行切面的增强逻辑。
步骤:
- 使用者调用
UserService
中的方法时,代理对象会拦截这个方法调用。 - 代理对象检查方法是否匹配切入点(Pointcut)。如果该方法匹配切入点条件(例如
execution(* com.example.service.UserService.*(..))
),则会执行对应的通知逻辑(Advice)。 - 根据 AOP 的配置,代理对象会在方法执行的不同阶段插入增强逻辑,例如:
- 在方法执行之前插入前置通知(
@Before
)。 - 在方法执行之后插入后置通知(
@After
)。 - 如果方法抛出异常,执行异常通知(
@AfterThrowing
)。
- 在方法执行之前插入前置通知(
6.3 织入切面逻辑
代理对象在方法调用前后,会根据切入点匹配情况自动织入相应的切面逻辑。
示例切面:
@Aspect
@Component
public class LoggingAspect {// 定义一个切入点,匹配 UserService 中的所有方法@Pointcut("execution(* com.example.service.UserService.*(..))")public void userServiceMethods() {}// 前置通知:在方法执行之前执行日志记录@Before("userServiceMethods()")public void logBefore(JoinPoint joinPoint) {System.out.println("开始执行方法: " + joinPoint.getSignature().getName());}// 后置通知:在方法执行之后执行日志记录@After("userServiceMethods()")public void logAfter(JoinPoint joinPoint) {System.out.println("方法执行结束: " + joinPoint.getSignature().getName());}
}
步骤:
- 在代理对象拦截
UserService.registerUser()
方法调用时,它首先会执行LoggingAspect
中定义的前置通知(@Before
)。这里会记录日志,表示方法的开始。 - 代理对象接着调用
UserService
的实际registerUser()
方法,执行核心业务逻辑。 - 方法执行完毕后,代理对象执行后置通知(
@After
),记录日志,表示方法结束。 - 如果方法在执行过程中抛出异常,代理对象还会执行异常通知(如果有定义)。
6.4 业务逻辑方法执行
在切面(如日志、事务等)逻辑执行完毕后,代理对象会继续执行实际的业务方法。这时代理对象的行为和直接调用 UserService
没有区别,只不过在此之前或之后已经插入了额外的横切关注点逻辑。
6.5 方法结束后的后置逻辑
业务逻辑执行完毕后,代理对象还会检查是否有后续的切面逻辑要执行。如果有定义 @After
或 @AfterReturning
,它会执行这些后置通知。
后置通知的触发:
- 方法执行结束后,代理对象执行后置通知(如
@After
),记录方法结束的日志或执行其他横切关注点逻辑。 - 如果方法抛出异常,代理对象会执行
@AfterThrowing
通知,进行异常处理或日志记录。
6.6 @Service
与 AOP 的结合流程
- Spring 容器扫描:Spring 容器扫描
@Service
注解的类,并将其注册为 Bean。 - 代理对象生成:如果有匹配的 AOP 切面,Spring 会为
UserService
生成代理对象。 - 方法调用拦截:当使用者调用
UserService
的方法时,代理对象拦截方法调用。 - 织入切面逻辑:代理对象根据切入点匹配情况,在方法执行前后或抛出异常时,织入切面逻辑(如日志、事务、权限校验等)。
- 业务逻辑执行:代理对象执行
UserService
的实际业务逻辑。 - 后置逻辑执行:方法执行完毕后,代理对象继续执行后置通知或异常处理逻辑。