Spring Service中的@Service注解的使用

@Service注解是Spring框架中用于标识业务逻辑层 (Service层)的注解。它是Spring组件扫描机制的一部分,表明这个类包含业务逻辑,并且应该由Spring容器管理为一个Spring Bean 。它与@Component类似,都是标识一个类为Spring管理的Bean,但@Service通常用于专门标识业务逻辑类

1. @Service的基本功能

@Service是一个特殊的@Component,它本质上是@Component的派生注解。通过使用@Service,我们可以告诉Spring容器去自动扫描和注册这些类为Bean,供依赖注入使用。

java 复制代码
@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构造器注入(推荐方式)

使用构造器注入能确保依赖在类实例化时就被正确注入,并且方便进行单元测试。

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);
    }
}

@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直接注入到类的成员变量中。这是最常见但不推荐的方式,因为它使得依赖关系不那么显式,并且在单元测试中可能不太灵活。

java 复制代码
@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注解使用在类的成员变量之前。

直接对你所创建的成员变量进行依赖注入,相当于private UserService userService = new UserService();

2.1.3Setter注入

通过提供一个setter方法来注入依赖,尽管使用频率较低,但它可以在某些需要动态设置依赖的场景中使用。

java 复制代码
@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注解都是用在函数之前,依赖注入都是对方法中的参数进行依赖注入。

只不过唯一的区别就是构造器注入是在你创建对象的时候会自动对成员变量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标识的类。

java 复制代码
// 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中的业务逻辑方法。

场景2:服务层之间相互调用

在复杂的业务场景中,一个Service类可能会依赖另一个Service类,这时也可以使用@Autowired进行注入。

java 复制代码
// 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区分多个Bean

在某些情况下,如果Spring容器中有多个同类型的Bean(例如多个@Service),需要通过@Qualifier注解来明确指定注入的具体Bean。

java 复制代码
@Service("basicOrderService")
public class BasicOrderService implements OrderService {
    // 实现逻辑
}

@Service("advancedOrderService")
public class AdvancedOrderService implements OrderService {
    // 实现逻辑
}

@Service
public class PaymentService {

    private final OrderService orderService;

    @Autowired
    public PaymentService(@Qualifier("basicOrderService") OrderService orderService) {
        this.orderService = orderService;
    }

    // 业务逻辑
}
  • 通过@Qualifier("basicOrderService"),我们指定注入的具体实现类BasicOrderService

2.3.2结合@Primary注解

@Primary注解用于标识在多个相同类型的Bean中优先注入某个Bean。如果没有使用@Qualifier指定Bean,Spring会注入@Primary标注的Bean。

java 复制代码
@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(面向切面编程)**来实现的。以下是事务管理的基本流程:

  1. AOP代理对象 :当Spring启动时,它会通过AOP为标记了@Transactional的方法或类生成代理对象。这些代理对象会拦截对方法的调用。
  2. 事务开始 :当代理对象检测到对标记了@Transactional的方法的调用时,它会在方法执行前开启一个事务。
  3. 方法执行:方法执行过程中,Spring会暂时保存所有对数据库的操作,并等待方法的最终结果来决定是否提交或回滚。
  4. 提交或回滚:如果方法执行成功(没有抛出异常),Spring会提交事务;如果方法执行过程中抛出异常,则回滚事务。

3.3 @Transactional的不同应用方式

@Transactional可以作用于类级别方法级别,它们的行为略有不同。

3.3.1 类级别的@Transactional

如果在类上应用@Transactional,则该类中的所有公共方法都将自动包含事务管理。每次调用该类的公共方法时,Spring都会自动开启一个事务,方法执行成功时提交事务,方法执行失败时回滚事务。

java 复制代码
@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类中的所有公共方法都会被事务管理。当方法被调用时,事务将自动开启;如果方法执行失败(抛出异常),事务将回滚;如果方法执行成功,事务将提交。

3.3.2 方法级别的@Transactional

如果你不想对整个类的所有方法都使用事务管理,可以将@Transactional仅应用于特定方法。在这种情况下,只有被标记的方法会执行事务管理。

java 复制代码
@Service
public class OrderService {

    @Autowired
    private 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能够自动管理业务逻辑中的事务。常见的场景是,当业务逻辑涉及多个数据库操作(如插入、更新、删除)时,如果某个操作失败,事务可以回滚,从而保证数据一致性。

示例代码:

java 复制代码
@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方法调用时拦截并启动事务。
    • 作用:确保该方法内的所有操作作为一个整体,要么全部成功,要么全部失败。如果方法内部任何地方发生异常或错误,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:如果当前有事务,则支持当前事务;如果没有事务,也可以不使用事务。
java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAccount(Account account) {
    accountRepository.save(account);
}

3.5.2 isolation(隔离级别)

  • 定义数据库操作之间的隔离程度,防止脏读、不可重复读和幻读等问题。
  • 常见的隔离级别:
    • READ_UNCOMMITTED:最低的隔离级别,可能会出现脏读。
    • READ_COMMITTED:保证读取的数据是已提交的,防止脏读。
    • REPEATABLE_READ:同一个事务中多次读取相同的数据结果相同,防止不可重复读。
    • SERIALIZABLE:最高的隔离级别,防止幻读。

示例:

java 复制代码
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateAccount(Account account) {
    accountRepository.save(account);
}

3.5.3 timeout(超时)

  • 定义事务的超时时间,如果事务在指定的时间内没有完成,将会回滚。

示例:

java 复制代码
@Transactional(timeout = 30)  // 超时时间为30秒
public void performLongRunningTask() {
    // 执行耗时任务
}

3.5.4 readOnly(只读事务)

  • 如果事务只进行查询操作而不进行更新,readOnly = true可以优化性能。

示例:

java 复制代码
@Transactional(readOnly = true)
public Account getAccount(Long accountId) {
    return accountRepository.findById(accountId).orElseThrow();
}

3.5.5 rollbackFornoRollbackFor

默认情况下,Spring的事务管理机制只会在运行时异常(RuntimeExceptionError 发生时回滚事务。而对于受检异常(Checked Exception ,Spring不会自动回滚,除非你显式地配置rollbackFor属性来指定。

  • rollbackFor:指定哪些异常会导致事务回滚,默认情况下,只有未捕获的运行时异常会导致回滚。
  • noRollbackFor:指定哪些异常不会导致事务回滚。

示例:

java 复制代码
@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都会返回同一个实例。
  • 优点:单例模式节省内存和提高性能,因为在整个应用生命周期中只有一个实例。

示例:

java 复制代码
@Service
public class UserService {
    // 默认是 singleton
}

singleton作用域下,Spring在启动时创建UserService实例,并在整个应用程序中共享同一个实例。

4.1.2 prototype(原型作用域)

  • 多实例模式:每次需要这个Bean时,Spring都会创建一个新的实例。
  • 使用场景 :当你希望每次访问时都能获得一个新的@Service对象实例,而不是共享一个单例。
  • 注意 :Spring仅负责创建新实例,不管理Bean的生命周期(如销毁)。因此,使用prototype时,Bean的销毁需要手动处理。

示例:

java 复制代码
@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应用。

示例:

java 复制代码
@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实例时使用。

示例:

java 复制代码
@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容器管理,它的生命周期包括创建、初始化、使用和销毁几个阶段。具体的生命周期步骤如下:

  1. 创建(Bean的实例化)

    • 当Spring容器启动时,它会扫描所有带有@Service注解的类,并为每个类创建一个实例(singleton作用域下)。这个过程称为实例化,即Spring在内存中为这个类分配空间并创建它的对象。
  2. 初始化(Bean的初始化)

    • 当Bean被实例化后,Spring会调用它的初始化方法(如果有),如@PostConstruct。初始化方法用于配置Bean的初始状态。

    示例:

    java 复制代码
    @Service
    public class UserService {
    
        @PostConstruct
        public void init() {
            // 初始化逻辑
            System.out.println("UserService初始化");
        }
    }

    在这个例子中,当UserService类被实例化后,init()方法会被调用来完成初始化操作。

  3. 使用(Bean的使用)

    • 初始化完成后,Bean会被注入到需要使用它的类中。例如,@Autowired注解会在依赖注入时将Bean注入到控制器或其他服务中,供业务逻辑使用。
    • 在使用过程中,Bean的实例会参与业务逻辑的处理,并与其他组件协作。
  4. 销毁(Bean的销毁)

    • 当Spring容器关闭时,Spring会调用Bean的销毁方法(如果有),如@PreDestroy
    • 这种销毁通常只适用于singleton作用域的Bean,因为prototype作用域的Bean销毁需要手动处理。

    示例:

    java 复制代码
    @Service
    public class UserService {
    
        @PreDestroy
        public void destroy() {
            // 销毁逻辑
            System.out.println("UserService销毁");
        }
    }

    在这个例子中,当Spring容器关闭时,destroy()方法会被调用,执行资源释放或清理操作。

4.3 Bean生命周期的详细流程

@Service为例,它的完整生命周期流程如下:

  1. Spring扫描并发现@Service Bean
  2. 实例化Bean:Spring使用默认构造函数(或其他指定构造函数)创建该类的实例。
  3. 依赖注入 :通过@Autowired将该类的依赖注入(如Repository或其他@Service类)。
  4. 初始化Bean :Spring调用Bean的初始化方法(如@PostConstruct)。
  5. Bean的使用:Bean被注入到控制器或其他类中,并执行相应的业务逻辑。
  6. 销毁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会在容器关闭时自动调用这个方法,用于释放资源或执行一些关闭操作。

java 复制代码
@Service
@Scope("prototype")
public class PrototypeService {

    @PostConstruct
    public void init() {
        // 初始化逻辑
        System.out.println("PrototypeService 初始化");
    }

    @PreDestroy
    public void cleanup() {
        // 清理逻辑
        System.out.println("PrototypeService 销毁");
    }
}

在这个例子中,PrototypeService的作用域是prototype,因此每次注入都会创建一个新实例。init()方法在实例创建时被调用,而cleanup()方法不会被自动调用,因为prototype作用域的Bean需要手动管理其销毁。

  • @Service:标识服务层组件,由Spring管理其生命周期。
  • 作用域 :控制Spring如何管理Bean的实例,默认是singleton,还可以选择prototyperequestsession等作用域。
  • 生命周期 :Spring负责Bean的创建、初始化、使用和销毁,开发者可以通过回调方法(如@PostConstruct@PreDestroy)在生命周期的关键时刻执行自定义逻辑。

5. 自定义服务名称

虽然默认情况下,@Service会以类名的小写形式将类注册为Spring容器中的Bean,但可以通过显式指定Bean的名称。

java 复制代码
@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 相关的切面逻辑,为这个类生成代理对象。

步骤

  1. Spring 检查是否有任何切面(Aspect)应用于 UserService 这样的 @Service 类。切面通常通过 @Aspect 注解定义,描述在哪些方法或类上插入横切逻辑。
  2. 如果有匹配的切面,Spring 使用动态代理机制为 UserService 生成代理对象。

生成的代理对象代替了原来的 UserService Bean,Spring 容器中保存的实际上是这个代理对象,而不是直接的 UserService 实例。

6.2 方法调用时的代理行为

当使用者调用 UserService 中的方法时,实际上是通过代理对象进行调用,而不是直接调用 UserService 实例。代理对象会拦截这个方法调用,并根据 AOP 的切面逻辑决定是否执行切面的增强逻辑。

步骤

  1. 使用者调用 UserService中的方法时,代理对象会拦截这个方法调用。
  2. 代理对象检查方法是否匹配切入点(Pointcut)。如果该方法匹配切入点条件(例如 execution(* com.example.service.UserService.*(..))),则会执行对应的通知逻辑(Advice)。
  3. 根据 AOP 的配置,代理对象会在方法执行的不同阶段插入增强逻辑,例如:
    • 在方法执行之前插入前置通知(@Before)。
    • 在方法执行之后插入后置通知(@After)。
    • 如果方法抛出异常,执行异常通知(@AfterThrowing)。

6.3 织入切面逻辑

代理对象在方法调用前后,会根据切入点匹配情况自动织入相应的切面逻辑。

示例切面

java 复制代码
@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());
    }
}

步骤

  1. 在代理对象拦截 UserService.registerUser() 方法调用时,它首先会执行 LoggingAspect 中定义的前置通知(@Before)。这里会记录日志,表示方法的开始。
  2. 代理对象接着调用 UserService 的实际 registerUser() 方法,执行核心业务逻辑。
  3. 方法执行完毕后,代理对象执行后置通知(@After),记录日志,表示方法结束。
  4. 如果方法在执行过程中抛出异常,代理对象还会执行异常通知(如果有定义)。

6.4 业务逻辑方法执行

在切面(如日志、事务等)逻辑执行完毕后,代理对象会继续执行实际的业务方法。这时代理对象的行为和直接调用 UserService 没有区别,只不过在此之前或之后已经插入了额外的横切关注点逻辑。

6.5 方法结束后的后置逻辑

业务逻辑执行完毕后,代理对象还会检查是否有后续的切面逻辑要执行。如果有定义 @After@AfterReturning,它会执行这些后置通知。

后置通知的触发

  1. 方法执行结束后,代理对象执行后置通知(如 @After),记录方法结束的日志或执行其他横切关注点逻辑。
  2. 如果方法抛出异常,代理对象会执行 @AfterThrowing 通知,进行异常处理或日志记录。

6.6 @Service 与 AOP 的结合流程

  1. Spring 容器扫描 :Spring 容器扫描 @Service 注解的类,并将其注册为 Bean。
  2. 代理对象生成 :如果有匹配的 AOP 切面,Spring 会为 UserService 生成代理对象。
  3. 方法调用拦截 :当使用者调用 UserService 的方法时,代理对象拦截方法调用。
  4. 织入切面逻辑:代理对象根据切入点匹配情况,在方法执行前后或抛出异常时,织入切面逻辑(如日志、事务、权限校验等)。
  5. 业务逻辑执行 :代理对象执行 UserService 的实际业务逻辑。
  6. 后置逻辑执行:方法执行完毕后,代理对象继续执行后置通知或异常处理逻辑。
相关推荐
zquwei7 分钟前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
TT哇14 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
火烧屁屁啦36 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
w_31234541 小时前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
北辰浮光1 小时前
[spring]XML配置文件标签
xml·spring
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
Q_19284999061 小时前
基于Spring Boot的营销项目系统
spring boot
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi