Spring高手之路-Spring在业务中常见的使用方式

这篇具有很好参考价值的文章主要介绍了Spring高手之路-Spring在业务中常见的使用方式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

通过IOC实现策略模式

通过AOP实现拦截增强

1.参数检验

2.缓存逻辑

3.日志记录

通过Event异步解耦

通过Spring管理事务

1.声明式事务

2.编程式事务

3.需要注意的问题

不能在事务中处理分布式缓存

不能在事务中执行 RPC 操作

不过度使用声明式事务


通过IOC实现策略模式

很多时候,我们需要对不同的场景进行不同的业务逻辑处理,举个例子,譬如针对不同类型的用户,购买商品的折扣不同。

普通的逻辑是使用if-else如下:

        //其他逻辑。。。。。。。。。
        double discount;
        if(userType==NORMAL){
            //打九折
            discount = 0.9;
        }
        else if(userType==VIP){
            //打八折
            discount = 0.8;
        }
        //其他逻辑。。。。。。。。。

随着升级扩展可能会新增用户类型,比如超级会员,打七折。。。。。。。这种if-else逻辑显然不够优雅。

我们可以借助Spring IOC实现策略模式进行优化,只需要将不同的策略类定义成 Spring Bean,然后在需要使用策略的地方通过 IOC 容器获取对应的 Bean 即可。如下步骤

定义折扣策略接口:

public interface DiscountStrategy {
    double calculateDiscount(double price);
}

普通会员折扣策略:

@Component("normalDiscount")
public class NormalDiscountStrategy implements DiscountStrategy {
    @Override
    public double calculateDiscount(double price) {
        // 普通会员打九折
        return price * 0.9;
    }
}

vip会员折扣策略:

@Component("vipDiscount")
public class VipDiscountStrategy implements DiscountStrategy {
    @Override
    public double calculateDiscount(double price) {
        // VIP会员打八折
        return price * 0.8;
    }
}

使用策略:

@Component
public class ShoppingService {
    @Autowired
    private DiscountStrategy discountStrategy;

    public double calculateFinalPrice(double price) {
        // 根据不同的策略计算折扣价
        double discountPrice = discountStrategy.calculateDiscount(price);
        // 其他计算逻辑...
        return discountPrice;
    }
}

通过AOP实现拦截增强

很多时候,我们一般是通过注解和AOP相结合。大概的实现思路就是先定义一个注解,然后通过AOP去发现使用过该注解的类,对该类的方法进行代理处理,增加额外的逻辑,譬如参数校验,缓存,日志打印等等。

1.参数检验

创建一个自定义的注解@ValidParams来标记需要进行参数校验的方法

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidParams {
}

创建一个切面类来拦截带有@ValidParams注解的方法,并在方法执行前进行参数校验


@Aspect
@Component
public class ValidationAspect {

    @Before("@annotation(com.example.ValidParams)")
    public void validateParams(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        
        // 检查参数是否符合要求
        for (Object arg : args) {
            if (arg == null || !isValid(arg)) {
                throw new IllegalArgumentException("Invalid parameter");
            }
        }
    }

    private boolean isValid(Object arg) {
        // 在这里实现具体的参数校验逻辑
        // 返回true表示参数有效,返回false表示参数无效
        // 可根据实际需求进行定制化的参数校验逻辑
        // 这里只是一个示例,实际使用时需要根据具体情况进行修改
        return arg != null;
    }
}

2.缓存逻辑

创建自定义缓存注解@@CacheableRedis

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheableRedis {
    String key();
    int expireTime() default 3600;
}

创建一个切面类,用于拦截带有@CacheableRedis注解的方法,并实现缓存逻辑

@Aspect
@Component
public class CacheAspect {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Before("@annotation(cacheableRedis)")
    public Object cache(JoinPoint joinPoint, CacheableRedis cacheableRedis) {
        String key = cacheableRedis.key();
        int expireTime = cacheableRedis.expireTime();
        
        Object result = redisTemplate.opsForValue().get(key);
        //缓存中有数据直接返回
        if (result != null) {
            return result;
        }
        //没有就访问数据库获取,存到缓存里面
        result = joinPoint.proceed();
        redisTemplate.opsForValue().set(key, result, expireTime, TimeUnit.SECONDS);
        return result;
    }
}

3.日志记录

@Aspect
@Component
public class LogAspect {
    @Pointcut("execution(* com.example.service.*Service.*(..))")
    public void servicePointcut() {}

    @Before("servicePointcut()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("执行 " + joinPoint.getSignature().getName() + " 方法前记录日志");
    }

    @AfterReturning("servicePointcut()")
    public void logAfterReturning(JoinPoint joinPoint) {
        System.out.println("执行 " + joinPoint.getSignature().getName() + " 方法后记录日志");
    }

    @AfterThrowing(value = "servicePointcut()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("执行 " + joinPoint.getSignature().getName() + " 方法发生异常,异常信息为:" + ex.getMessage());
    }
}

@Service
public class UserService {
    public void addUser(User user) {
        // 这里是添加用户的逻辑
    }
}

在上面的示例中,我们定义了一个名为 LogAspect 的切面类,并通过 @Pointcut 注解定义了一个切点,表示需要拦截的方法。在 LogAspect 中,我们使用 @Before@AfterReturning@AfterThrowing 注解分别定义了在方法执行前、执行后和发生异常时需要执行的增强方法。在 UserService 中,我们调用了 addUser() 方法,该方法将会被 LogAspect 中的增强方法拦截。

通过Event异步解耦

很多时候,可以一个单据状态的改变,要触发很多下游的行为,举个例子:

订单从确认订单变为支付成功,就要触发物流的发货,财务的记账,EDM触达(通过电子直邮(Electronic Direct Mail)的方式向目标受众发送信息。)等等。但是如果订单状态改变同步触发下游的动作,这样对订单业务非常不友好,下游的每次变动都需要上游感知。所以,对于这种情况,我们就需要Event异步解藕。

首先,定义订单状态改变事件类:

public class OrderStatusChangeEvent extends ApplicationEvent {
    private Long orderId;
    private String newStatus;
    
    public OrderStatusChangeEvent(Object source, Long orderId, String newStatus) {
        super(source);
        this.orderId = orderId;
        this.newStatus = newStatus;
    }
    
    // 省略getter/setter方法
}

然后,创建一个事件发布者:

@Component
public class OrderEventPublisher {
    private final ApplicationEventPublisher eventPublisher;
    
    public OrderEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    
    public void publishOrderStatusChangeEvent(Long orderId, String newStatus) {
        OrderStatusChangeEvent event = new OrderStatusChangeEvent(this, orderId, newStatus);
        eventPublisher.publishEvent(event);
    }
}

接下来,定义物流发货、财务记账和EDM触达的事件监听器:

@Component
public class ShippingEventListener {
    @EventListener
    @Async
    public void handleShippingEvent(OrderStatusChangeEvent event) {
        Long orderId = event.getOrderId();
        System.out.println("订单 " + orderId + " 物流发货");
    }
}

@Component
public class AccountingEventListener {
    @EventListener
    @Async
    public void handleAccountingEvent(OrderStatusChangeEvent event) {
        Long orderId = event.getOrderId();
        System.out.println("订单 " + orderId + " 财务记账");
    }
}

@Component
public class EDMEventListener {
    @EventListener
    @Async
    public void handleEDMEvent(OrderStatusChangeEvent event) {
        Long orderId = event.getOrderId();
        System.out.println("订单 " + orderId + " EDM触达");
    }
}

最后,在需要改变订单状态的地方,注入事件发布者并触发订单状态改变事件:

@Service
public class OrderService {
    private final OrderEventPublisher eventPublisher;
    
    public OrderService(OrderEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    
    public void changeOrderStatus(Long orderId, String newStatus) {
        // 执行订单状态改变逻辑
        
        // 发布订单状态改变事件
        eventPublisher.publishOrderStatusChangeEvent(orderId, newStatus);
    }
}

通过Spring管理事务

Spring的事务抽象了下游不同DataSource的实现 (如,JDBC,Mybatis,Hibernate等),让我们不用再关心下游的事务提供方究竟是谁,直接启动事务即可。

1.声明式事务

声明式事务是指在方法或类级别上添加@Transactional注解来实现事务管理。这种方式需要使用Spring的AOP机制来实现,在方法调用前后自动开启和提交事务,同时还能够处理事务回滚等异常情况。

例如,我们可以在Service层中添加@Transactional注解来实现事务管理:

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    @Transactional(rollbackFor = Exception.class)
    public void addUser(User user) {
        userDao.addUser(user);
    }
}

需要注意的是使用声明式事务不当也会让事务失效具体可以看:

Spring高手之路-Spring事务失效的场景详解-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135614523?spm=1001.2014.3001.5502

2.编程式事务

编程式事务是指通过编写代码来实现事务管理,通常是在Service层中手动开启、提交和回滚事务。虽然这种方式比较繁琐,但是在某些场景下仍然很有用。

例如,我们可以在Service层中使用TransactionTemplate类来实现编程式事务管理:

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void addUser(final User user) {
        transactionTemplate.execute(new TransactionCallback<Void>() {
            public Void doInTransaction(TransactionStatus status) {
                try {
                    userDao.addUser(user);
                } catch (Exception e) {
                    status.setRollbackOnly();
                    throw e;
                }
                return null;
            }
        });
    }
}

3.需要注意的问题

不能在事务中处理分布式缓存

如果在事务中进行了缓存操作,但事务最终被回滚了,那么缓存中就可能存在脏数据,进而影响业务逻辑。

不能在事务中执行 RPC 操作

在事务中执行 RPC 操作,会增加事务的执行时间,尤其是当 RPC 服务不可用或响应很慢时,会导致事务长时间占用资源,进而影响系统性能和稳定性。此外,在需要回滚事务时,RPC 调用可能无法回滚,进而造成数据一致性问题。

不过度使用声明式事务

过多地使用声明式事务,会增加系统的复杂度,使得代码难以理解和维护。

声明式事务可能会引起死锁等性能问题,尤其是在高并发环境下。

对于复杂的事务场景,声明式事务可能无法满足需求,需要使用编程式事务。文章来源地址https://www.toymoban.com/news/detail-804076.html

到了这里,关于Spring高手之路-Spring在业务中常见的使用方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring高手之路-SpringBean的生命周期

    目录 SpringBean的生命周期 整体介绍 详细介绍 1.实例化Bean 2.设置属性值 3.检查Aware 4.调用BeanPostProcessor的前置处理方法 5.调用InitializingBean的afterPropertiesSet方法 6.调用自定义init-method方法 7.调用BeanPostProcessor的后置处理方法 8.注册Destruction回调 9.Bean准备就绪 10.调用DisposableBean的d

    2024年02月03日
    浏览(28)
  • Spring高手之路3——揭秘Spring依赖注入和SpEL表达式

    本篇会给大家举出各种 Spring 属性依赖注入的例子,方便大家理解。 我们在前面的文章中已经使用过 XML 进行 setter 方法的属性注入了,下面让我们再来回顾一下: 我们在前面的文章中也学习过如何在 bean 创建时通过编程方式设置属性: 使用XML进行setter方法注入 首先,我们需

    2024年02月08日
    浏览(32)
  • Spring高手之路8——Spring Bean模块装配的艺术:@Import详解

      在 Spring 中,手动装配通常是指通过 XML 配置文件明确指定 Bean 及其依赖,或者在代码中直接使用 new 创建对象并设定依赖关系。   然而,随着 Spring 2.0 引入注解,以及 Spring 3.0 全面支持注解驱动开发,这个过程变得更加自动化。例如,通过使用 @Component + @Compo

    2024年02月13日
    浏览(24)
  • Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石

       BeanDefinition 是 Spring 中一个非常重要的概念,它包含了 Spring 容器用于创建、配置 Bean 所需的所有信息。理解 BeanDefinition 可以帮助我们深入掌握 Spring 的内部工作机制。 首先,让我们来对 BeanDefinition 有一个整体的认识。   对于理解 Spring 框架的概念和组件, Spring 的官方

    2024年02月15日
    浏览(35)
  • Spring高手之路15——掌握Spring事件监听器的内部逻辑与实现

    在阅读本文之前需要你已经对事件监听器有了简单的了解,或去阅读前面的文章《 Spring高手之路7——事件机制与监听器的全面探索 》   在 Spring 中, ApplicationContext 可以形成一个层次结构,通常由主容器和多个子容器组成。一个常见的疑问是:当一个事件在其中一个容器

    2024年02月06日
    浏览(34)
  • Spring高手之路-@Autowired和@Resource注解异同点

    目录 概述 相同点 1.都可以实现依赖注入 2.都可以用于注入任意类型的Bean 3.都支持通过名称、类型匹配进行注入 不同点 1.来源不同。 2.包含的属性不同 3.匹配方式(装配顺序)不同。 4.支持的注入对象类型不同 5.应用地方不同 @Autowired 和 @Resource 是在 Java 开发中用于实现依赖

    2024年02月03日
    浏览(37)
  • Spring高手之路5——彻底掌握Bean的生命周期

    在 Spring IOC 容器中, Bean 的生命周期大致如下: 实例化:当启动 Spring 应用时, IOC 容器就会为在配置文件中声明的每个 bean 创建一个实例。 属性赋值:实例化后, Spring 就通过反射机制给 Bean 的属性赋值。 调用初始化方法:如果 Bean 配置了初始化方法, Spring 就会调用它。

    2024年02月09日
    浏览(29)
  • Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析

       BeanDefinitionRegistry 是一个非常重要的接口,存在于 Spring 的 org.springframework.beans.factory.support 包中,它是 Spring 中注册和管理 BeanDefinition 的核心组件。   让我们回顾一下上一篇说的 BeanDefinition 。在 Spring 中,一个 Bean 就是一个被 Spring 管理的对象,而一个 BeanDefinition 则是

    2024年02月13日
    浏览(30)
  • Spring高手之路4——深度解析Spring内置作用域及其在实践中的应用

    我们来看看 Spring 内置的作用域类型。在 5.x 版本中, Spring 内置了六种作用域: singleton :在 IOC 容器中,对应的 Bean 只有一个实例,所有对它的引用都指向同一个对象。这种作用域非常适合对于无状态的 Bean ,比如工具类或服务类。 prototype :每次请求都会创建一个新的 Be

    2024年02月08日
    浏览(30)
  • Spring高手之路13——BeanFactoryPostProcessor与BeanDefinitionRegistryPostProcessor解析

       BeanFactoryPostProcessor 位于 org.springframework.beans.factory.config 包中。它与 BeanPostProcessor 有相似的核心逻辑,但它们之间的主要区别在于它们所操作的对象。 BeanFactoryPostProcessor 的主要目的是对 Bean 的配置元数据进行操作,这意味着它可以影响 Bean 的初始配置数据。   在 Sp

    2024年02月11日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包