Spring Service中的@Service注解的使用小结

 更新时间:2024年11月25日 09:17:42   作者:阿乾之铭  
本文主要介绍了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用法

使用场景

@Autowired 一般用于注入其他 Spring 容器管理的 Bean,适用于以下场景:

  • 服务类之间的依赖:当一个服务类依赖于另一个服务类时。
  • 控制器类依赖服务类:在 Web 应用中,控制器通常需要调用服务类。
  • 服务类依赖数据访问层:使用 @Autowired 将数据访问层(例如 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注解使用在构造函数前。

当你在构造函数的参数中将经过@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 {

    @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方法来注入依赖,尽管使用频率较低,但它可以在某些需要动态设置依赖的场景中使用。

@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进行赋值。

所以这种依赖注入方式一般不用。

2.1.4 不需要@Autowired注解的情况

1. 构造函数注入

从Spring 4.3开始,如果一个Bean只有一个构造函数,Spring会自动使用该构造函数进行依赖注入,无需@Autowired注解。

@Service
public class UserService {
    private final UserRepository userRepository;

    // 不需要@Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

2. 单一实现的接口

如果一个接口只有一个实现类,Spring会自动将这个实现类注入到需要该接口的地方,无需额外配置。

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作为参数,Spring会自动注入。

@Configuration
public class DatabaseConfig {
    @Bean
    public DataSource dataSource() {
        // 创建并返回DataSource
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        // Spring自动注入上面定义的dataSource
        return new JdbcTemplate(dataSource);
    }
}

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;

    @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进行注入。

// 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。

@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。

@Service
@Primary
public class BasicOrderService implements OrderService {
    // 实现逻辑
}

@Service
public class AdvancedOrderService implements OrderService {
    // 实现逻辑
}
  • BasicOrderService标注了@Primary,因此在没有指定@Qualifier的情况下,Spring会优先注入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 指定默认值,避免出现 null 值。

示例

@Service
public class MyService {

    // 如果myapp.url没有定义,将使用默认值 "http://localhost"
    @Value("${myapp.url:http://localhost}")
    private String appUrl;
}

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 {

    @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仅应用于特定方法。在这种情况下,只有被标记的方法会执行事务管理。

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

示例代码: 

@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:如果当前有事务,则支持当前事务;如果没有事务,也可以不使用事务。
@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所有应用每次请求时创建新实例由容器外管理适合需要每次调用时生成新对象的场景
requestWeb应用每次HTTP请求时创建新实例HTTP请求范围内适用于每个HTTP请求需要独立状态的场景
sessionWeb应用每个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在内存中为这个类分配空间并创建它的对象。
  • 初始化(Bean的初始化)

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

    示例:

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

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

  • 使用(Bean的使用)

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

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

    示例:

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

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

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 {

    @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的名称。

@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 的实际业务逻辑。
  • 后置逻辑执行:方法执行完毕后,代理对象继续执行后置通知或异常处理逻辑。

 到此这篇关于Spring Service中的@Service注解的使用小结的文章就介绍到这了,更多相关Spring @Service注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 全面解析Java8观察者模式

    全面解析Java8观察者模式

    这篇文章主要为大家全面解析Java8观察者模式,通过在 Java8 环境下实现观察者模式的实例,进一步介绍了什么是观察者模式、专业化及其命名规则,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • Java中常用时间的一些相关方法

    Java中常用时间的一些相关方法

    日期的使用多种多样,但万变不离其宗,下面这篇文章主要给大家介绍了关于Java中常用时间的一些相关方法,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-10-10
  • SpringBoot添加SSL证书,开启HTTPS方式(单向认证服务端)

    SpringBoot添加SSL证书,开启HTTPS方式(单向认证服务端)

    这篇文章主要介绍了SpringBoot添加SSL证书,开启HTTPS方式(单向认证服务端),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Spring中AOP概念与两种动态代理模式原理详解

    Spring中AOP概念与两种动态代理模式原理详解

    AOP是面向切面编程的技术,AOP基于IoC基础,是对OOP的有益补充,流行的AOP框架有Sping AOP、AspectJ,这篇文章主要给大家介绍了关于Spring中AOP概念与两种动态代理模式原理的相关资料,需要的朋友可以参考下
    2021-10-10
  • spring @profile注解的使用方法

    spring @profile注解的使用方法

    本篇文章主要介绍了spring @profile注解的使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • IDEA中添加xml配置文件时,显示file问题

    IDEA中添加xml配置文件时,显示file问题

    这篇文章主要介绍了IDEA中添加xml配置文件时,显示file问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Java方法重载和方法重写的区别到底在哪?

    Java方法重载和方法重写的区别到底在哪?

    今天给大家带来的是关于Java的相关知识,文章围绕着Java方法重载和方法重写的区别到底在哪展开,文中有非常详细的解释,需要的朋友可以参考下
    2021-06-06
  • 页面设计之事件处理综合介绍

    页面设计之事件处理综合介绍

    页面设计之事件处理,当你把界面都设计好了,总需要添加相应的执行动作给组件,在有相应的时间处理机制
    2012-12-12
  • Java并发编程线程间通讯实现过程详解

    Java并发编程线程间通讯实现过程详解

    这篇文章主要介绍了Java并发编程线程间通讯实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • 如何使用IntelliJ IDEA的HTTP Client进行接口验证

    如何使用IntelliJ IDEA的HTTP Client进行接口验证

    这篇文章主要介绍了如何使用IntelliJ IDEA的HTTP Client进行接口验证,本文给大家分享最新完美解决方案,感兴趣的朋友跟随小编一起看看吧
    2024-06-06

最新评论