java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring  @Service注解

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

作者:阿乾之铭

本文主要介绍了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,适用于以下场景:

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状态码、响应头、以及响应体。

 ResponseEntity的构造方法和常用方法:

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

场景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;
    }

    // 业务逻辑
}

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 {
    // 实现逻辑
}

2.4 @value注解进行单个属性的依赖注入 

2.4,1 基本用法:从 application.properties 中读取值

步骤

示例

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应该为该类或方法的操作启用事务。事务管理保证了业务逻辑中的多个操作要么全部成功,要么全部失败,这样可以保证数据的一致性。

3.2 Spring AOP(面向切面编程)与事务管理

Spring的事务管理机制是通过AOP(面向切面编程)来实现的。以下是事务管理的基本流程:

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注解为该方法开启一个事务

从数据库中获取付款方账户

扣除余额

保存更新后的付款方账户

事务提交

3.5 @Transactional的事务属性

@Transactional提供了多个属性,用来细化事务的管理行为。常用属性包括:

3.5.1 propagation(传播行为)

示例:

定义当前事务方法是否应该运行在一个现有的事务中,或者是否应该创建一个新的事务。

常见的传播属性:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAccount(Account account) {
    accountRepository.save(account);
}

3.5.2 isolation(隔离级别)

定义数据库操作之间的隔离程度,防止脏读、不可重复读和幻读等问题。

常见的隔离级别:

示例:

@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属性来指定。

示例:

@Transactional(rollbackFor = {Exception.class})
public void riskyOperation() throws Exception {
    // 执行可能抛出受检异常的操作
}

这个写法表示只要抛出了Exception类型或它的子类异常,Spring就会回滚事务。 

这里的Exception只是代指异常的类型,实际使用的时候要加入实际的异常类型,比如:SQLException(受检异常)或DataAccessException(运行时异常) 。

3.6 @Service与@Transactional的常见使用场景

4. 作用域与生命周期

4.1作用域(Scope)

作用域决定了Spring容器如何管理Bean的实例。Spring默认会为每个@Service Bean分配一个单例作用域(singleton),即整个应用程序中只有一个实例。但如果需要,Spring允许我们为@Service Bean设置其他作用域。

常见的作用域: 

4.1.1 singleton(单例,默认作用域)

示例:

@Service
public class UserService {
    // 默认是 singleton
}

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

4.1.2 prototype(原型作用域)

示例:

@Service
@Scope("prototype")
public class ReportService {
    // 每次注入时都会创建一个新实例
}

prototype作用域下,每次请求ReportService时,Spring都会创建一个新的实例。

4.1.3 request(仅适用于Web应用)

因为只有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应用):

示例:

@Service
@Scope(value = WebApplicationContext.SCOPE_SESSION)
public class SessionScopedService {
    // 在一个会话中,实例是共享的
}

4.1.5 总结 

作用域类型适用范围实例化频率生命周期适用场景
singleton所有应用容器启动时创建一个实例全局共享默认作用域,适用于大部分情况
prototype所有应用每次请求时创建新实例由容器外管理适合需要每次调用时生成新对象的场景
requestWeb应用每次HTTP请求时创建新实例HTTP请求范围内适用于每个HTTP请求需要独立状态的场景
sessionWeb应用每个HTTP会话创建新实例HTTP会话范围内适用于用户登录会话、购物车等需要在会话内共享的场景

4.2 生命周期(Lifecycle)

@Service Bean的生命周期受Spring容器管理,它的生命周期包括创建、初始化、使用和销毁几个阶段。具体的生命周期步骤如下:

4.3 Bean生命周期的详细流程

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

4.4 设置自定义的作用域和生命周期管理

通过结合@Scope和生命周期回调(如@PostConstruct@PreDestroy),你可以对@Service Bean的作用域和生命周期进行细粒度控制。

@PostConstruct 和 @PreDestroy 注解

@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 {
    // 业务逻辑
}

6. 与AOP(面向切面编程)的结合

此处只讲解了@Service注解与AOP结合使用时的具体流程,关于AOP的详细内容请查看AOP(面向切面编程) 

6.1 AOP 与 @Service 结合:生成代理对象

AOP 的核心在于为某些特定的类或方法(例如带有日志、事务等横切关注点的类或方法)创建代理对象。代理对象是指 Spring 在运行时为目标对象(例如 UserService)生成的一个增强版本,这个版本可以在方法执行的前后或异常时插入自定义的逻辑(切面)。

当 Spring 容器扫描到 @Service 注解的类时,会根据是否配置了 AOP 相关的切面逻辑,为这个类生成代理对象。

步骤

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

6.2 方法调用时的代理行为

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

步骤

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

步骤

6.4 业务逻辑方法执行

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

6.5 方法结束后的后置逻辑

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

后置通知的触发

6.6 @Service 与 AOP 的结合流程

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

您可能感兴趣的文章:
阅读全文