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博客icon-default.png?t=N7T8https://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 调用可能无法回滚,进而造成数据一致性问题。

不过度使用声明式事务

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

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

对于复杂的事务场景,声明式事务可能无法满足需求,需要使用编程式事务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/329414.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件工程应用题汇总

绘制数据流图(L0/L1/L2) DFD/L0&#xff08;基本系统模型&#xff09; 只包含源点终点和一个处理(XXX系统) DFD/L1&#xff08;功能级数据流图&#xff09;在L0基础上进一步划分处理(XXX系统) 个人理解 DFD/L2&#xff08;在L1基础上进一步分解后的数据流图&#xff09; 数据…

蓝桥杯备赛 day 3 —— 高精度(C/C++,零基础,配图)

目录 &#x1f308;前言&#xff1a; &#x1f4c1; 高精度的概念 &#x1f4c1; 高精度加法和其模板 &#x1f4c1; 高精度减法和其模板 &#x1f4c1; 高精度乘法和其模板 &#x1f4c1; 高精度除法和其模板 &#x1f4c1; 总结 &#x1f308;前言&#xff1a; 这篇文…

C#中对浮点数NaN,PositiveInfinity,NegativeInfinity的特殊处理

NAN NAN 整体意思为Not a Number 不是一个数&#xff0c; NaN&#xff08;Not a Number&#xff0c;非数&#xff09;是计算机科学中数值数据类型的一类值&#xff0c;表示未定义或不可表示的值。常在浮点数运算中使用。首次引入NaN的是1985年的IEEE 754浮点数标准。 EEE 75…

Linux Mii management/mdio子系统分析之六 fixed-mii_bus分析(mac2mac分析)

&#xff08;转载&#xff09;原文链接&#xff1a;[https://blog.csdn.net/u014044624/article/details/130674908] (https://blog.csdn.net/u014044624/article/details/130674908) 前面几章我们介绍了MDIO模块的大部分内容&#xff0c;针对mii_bus、mdio_bus、phy_device、p…

学习鸿蒙先解决这几个是关键问题~

HarmonyOS 是最近最火的操作系统&#xff0c;HarmonyOS 宣布删除 Android 代码之后&#xff0c;正式向世界上第三大操作系统有迈进了一步&#xff0c;HarmonyOS 前期为了完成从 Android 到 HarmonyOS 的过渡&#xff0c;在设计之初 HarmonyOS 采用了双框架架构设计。 从图中可以…

【栈】【字符串和int类型转化】Leetcode 150 逆波兰表达式求值

【栈】【字符串和int类型转化】Leetcode 150 逆波兰表达式求值 解法1 栈 ---------------&#x1f388;&#x1f388;题目链接 Leetcode 150 逆波兰表达式求值 &#x1f388;&#x1f388;------------------- 解法1 栈 字符串转化为int类型数据: Integer.parseInt(s) Long.p…

SpringBoot教程(十五) | SpringBoot集成RabbitMq

SpringBoot教程(十五) | SpringBoot集成RabbitMq RabbitMq是我们在开发过程中经常会使用的一种消息队列。今天我们来研究研究rabbitMq的使用。 rabbitMq的官网&#xff1a; rabbitmq.com/ rabbitMq的安装这里先略过&#xff0c;因为我尝试了几次都失败了&#xff0c;后面等我…

FPGA时序分析与时序约束(四)——时序例外约束

目录 一、时序例外约束 1.1 为什么需要时序例外约束 1.2 时序例外约束分类 二、多周期约束 2.1 多周期约束语法 2.2 同频同相时钟的多周期约束 2.3 同频异相时钟的多周期约束 2.4 慢时钟域到快时钟域的多周期约束 2.5 快时钟域到慢时钟域的多周期约束 三、虚假路径约…

网站SEO优化方案

1&#xff0c;去各类搜索引擎里面&#xff0c;注册你的站点 解决方案&#xff1a;注册地址&#xff1a;https://seo.chinaz.com/chinaz.com 2&#xff0c;网站地址使用 https 会增加搜索排名 解决方案&#xff1a;https:www.xxx.com 3&#xff0c;官网每个页面的 meta 里面&a…

牛客周赛 Round 10 解题报告 | 珂学家 | 三分模板 + 计数DFS + 回文中心扩展

前言 整体评价 T2真是一个折磨人的小妖精&#xff0c;写了两版DFS&#xff0c;第二版计数DFS才过。T3是三分模板&#xff0c;感觉也可以求导数。T4的数据规模才n1000&#xff0c;因此中心扩展的 O ( n 2 ) O(n^2) O(n2)当仁不让。 A. 游游的最长稳定子数组 滑窗经典题 从某个…

78、avx2 数据 load/store 向量化操作介绍

向量寄存器和一个最简单的寄存器-内存的存储器模型,查看上一节。 本节基于整个内存模型,介绍一下如何使用 avx2 向量指令集,来完成数据从内存到寄存器中的交互的。 load 操作 在改内存模型下,load 操作指将数据从内存中加载到寄存器中。 使用 C++ 代码实现如下: float…

REVIT二次开发修改轴网

REVIT二次开发修改轴网 步骤1 步骤2 步骤3 功能实现在这 using System; using System.Collections.Generic; using System.Linq; using

【实操】基于 GitHub Pages + Hexo 搭建个人博客

《开发工具系列》 【实操】基于 GitHub Pages Hexo 搭建个人博客 一、引言二、接入 Node.js2.1 下载并安装 Node.js2.2 环境变量配置 三、接入 Git3.1 下载并安装 Git3.2 环境变量配置 四、接入 Hexo4.1 安装 Hexo4.2 建站4.3 本地启动服务器 五、接入 GitHub Pages5.1 初识 G…

大模型学习与实践笔记(七)

一、环境配置 1.平台&#xff1a; Ubuntu Anaconda CUDA/CUDNN 8GB nvidia显卡 2.安装 # 构建虚拟环境 conda create --name xtuner0.1.9 python3.10 -y # 拉取 0.1.9 的版本源码 git clone -b v0.1.9 https://github.com/InternLM/xtuner# 从源码安装 XTuner pip insta…

【Proteus仿真】【Arduino单片机】汽车车窗除霜系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用LCD1602显示模块、光线传感器、DS18B20温度传感器、PCF8691 ADC模块、继电器加热模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD…

常考SQL

1 思维导图 2 题目 mysql8版本 1. 连续问题♥♥♥ 问题描述&#xff1a;如下数据为蚂蚁森林中用户领取的减少碳排放量&#xff0c;找出连续3天及以上减少碳排量在100以上的用户。 iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021…

pyqtgraph绘图类

pyqtgraph绘图类 pyqtgraph绘图有四种方法: 方法描述pyqtgraph.plot()创建一个新的QWindow用来绘制数据PlotWidget.plot()在已存在的QWidget上绘制数据PlotItem.plot()在已存在的QWidget上绘制数据GraphicsLayout.addPlot()在网格布局中添加一个绘图 上面四个方法都接收同样…

Python爬虫实战:IP代理池助你突破限制,高效采集数据

当今互联网环境中&#xff0c;为了应对反爬虫、匿名访问或绕过某些地域限制等需求&#xff0c;IP代理池成为了一种常用的解决方案。IP代理池是一个包含多个可用代理IP地址的集合&#xff0c;可以通过该代理池随机选择可用IP地址来进行网络请求。 IP代理池是一组可用的代理IP地址…

【Maven】008-Maven 私服搭建与使用

【Maven】008-Maven 私服搭建与使用 文章目录 【Maven】008-Maven 私服搭建与使用一、概述1、简介2、建立私服后依赖查找和下载逻辑第一步&#xff1a;请求本地仓库第二步&#xff1a;请求 Maven 私服第三步&#xff1a;请求外部远程仓库&#xff08;远程中央仓库等&#xff09…

SpringBoot教程(三) | Spring Boot初体验

SpringBoot教程(三) | Spring Boot初体验 上篇文章我们创建了SpringBoot 项目&#xff0c;并且进行了简单的启动。整个项目了里其实我们就动了两个文件&#xff0c;一个是pom.xml负责管理springboot的相关依赖&#xff0c;一个是springBoot的启动类。 pom文件中通过starter的…