Spring 在业务场景使用方式(IOC实现策略模式、AOP实现拦截、Event解耦等)

Spring 在业务场景使用方式(IOC实现策略模式、AOP实现拦截、Event解耦等)


引言

Spring 框架作为 Java 开发中的重要利器,不仅提供了依赖注入(DI)和面向切面编程(AOP)等核心特性,还提供了丰富的功能和组件,可以应对各种业务场景。本文将通过示例详细解析 Spring 在业务中的常见应用场景和用法,包括使用 IoC 实现策略模式、AOP 实现日志切面、监听器实现业务异步解耦,以及其他常见用法。


1. 使用 IoC 实现策略模式

很多时候,面对不同的业务场景需要不同的业务逻辑,为了避免使用if-else导致代码臃肿,不具备扩展性,我们一般会使用策略模式。策略模式是一种常见的设计模式,它定义了一系列的算法,并将每个算法封装起来,使它们可以相互替换。在 Spring 中,可以通过 IoC 容器来实现策略模式,使用法更加灵活方便。

通过实现InitializingBeanafterPropertiesSet方法完成bean的初始化, 示例代码:

typescript 复制代码
// 定义策略接口
public interface PaymentStrategy extends InitializingBean {
​
    void pay(BigDecimal amount);
​
    PayType getPayType();
​
    @Override
    default void afterPropertiesSet() throws Exception {
        PayFactory.register(getPayType(), this);
    }
}
​
// 实现具体的支付策略
@Component
public class CreditCardPayment implements PaymentStrategy {
​
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("使用信用卡支付:" + amount);
    }
​
    @Override
    public PayType getPayType() {
        return PayType.CREDIT;
    }
}
​
@Component
public class CashPayment implements PaymentStrategy {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("使用现金支付:" + amount);
    }
​
    @Override
    public PayType getPayType() {
        return PayType.CASH;
    }
}
​
// 创建和获取bean的类
public class PayFactory {
​
    private static final Map<PayType, PaymentStrategy> PAY_MAP = new HashMap<>();
​
    public static void register(PayType payType, PaymentStrategy strategy) {
        PAY_MAP.put(payType, strategy);
    }
​
    public static PaymentStrategy getStrategy(PayType payType) {
        return PAY_MAP.get(payType);
    }
}
​
// 使用策略的业务类
@Test
void strategy() {
    PaymentStrategy paymentStrategy = PayFactory.getStrategy(PayType.CASH);
    paymentStrategy.pay(BigDecimal.ONE);
}

2. 使用 AOP 实现拦截

在开发过程中,经常需要使用AOP将与业务无关的操作进行切面处理,以便于专注于业务代码的开发和维护。通常是通过注解和AOP结合,实现思路就是先定义一个注解,然后通过AOP去发现使用该注解的类或方法,增加额外的逻辑,如参数校验,缓存,日志等。

参数校验

代码示例:

less 复制代码
// 定义一个注解作用于方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamCheck {
}
​
// 编写一个切面,做参数校验处理
@Aspect
@Component
public class ParamCheckAspect {
​
    @Before("@annotation(com.example.ParamCheck)")
    public void beforeMethodExecution(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        ParamCheck annotation = method.getAnnotation(ParamCheck.class);
        if (annotation != null) {
            // 进行参数校验逻辑
            for (Object arg : args) {
                if (arg == null) {
                    throw new IllegalArgumentException("参数不能为空");
                }
                // 其他校验逻辑...
            }
        }
    }
}

缓存

定义一个注解用于标记缓存的方法,value是缓存的唯一标识,可通过expiration设置缓存过期时间。

less 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
    String value() default "";
    long expiration() default 60;
}

定义一个切面获取注解标注的方法,获取注解信息,如果缓存中有值,直接返回。如果缓存中没有或者过期,先执行方法,再将结果进行缓存。如果需要根据方法入参进行缓存对应的数据,可以自行扩展。

java 复制代码
@Aspect
@Component
public class CacheAspect {
​
    private final Map<String, CacheEntry> cache = new HashMap<>();
​
    @Around("@annotation(com.example.Cacheable) && execution(* *(..))")
    public Object cacheMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        Cacheable annotation = getCacheableAnnotation(joinPoint);
        String cacheKey = annotation.value();
        if (!cache.containsKey(cacheKey) || cache.get(cacheKey).isExpired()) {
            Object result = joinPoint.proceed();
            long expirationTime = System.currentTimeMillis() + annotation.expiration() * 1000;
            cache.put(cacheKey, new CacheEntry(result, expirationTime));
        }
        return cache.get(cacheKey).getResult();
    }
​
    // 获取Cacheable注解
    private Cacheable getCacheableAnnotation(ProceedingJoinPoint joinPoint) {
        return joinPoint.getTarget().getClass().getMethod(getMethodName(joinPoint)).getAnnotation(Cacheable.class);
    }
​
    private String getMethodName(ProceedingJoinPoint joinPoint) {
        return joinPoint.getSignature().getName();
    }
​
    // 存放缓存信息的对象,包括缓存结果和缓存过期时间
    private static class CacheEntry {
        private final Object result;
        private final long expirationTime;
​
        public CacheEntry(Object result, long expirationTime) {
            this.result = result;
            this.expirationTime = expirationTime;
        }
​
        public Object getResult() {
            return result;
        }
​
        public boolean isExpired() {
            return System.currentTimeMillis() > expirationTime;
        }
    }
}
​

日志打印

对于日志打印,实现也是类似,定义一个注解用于AOP发现,使用前置通知和返回通知处理日志信息。代码示例:

less 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}
​
@Aspect
@Component
public class LoggingAspect {
​
    private final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
​
    @Pointcut("@annotation(com.example.Loggable)")
    public void loggableMethods() {}
​
    @Before("loggableMethods()")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("Before executing method: {}", joinPoint.getSignature().getName());
    }
​
    @AfterReturning(pointcut = "loggableMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        logger.info("After executing method: {}, result: {}", joinPoint.getSignature().getName(), result);
    }
}

3. 使用监听器实现业务异步解耦

监听器(Listener)是 Spring 框架中的重要组件,可以实现事件驱动的编程模式,用于解耦业务逻辑。举个例子:订单从确认到支付成功,需要触发财务记账,仓库扣减等操作。如果订单状态的修改需要同步处理下游的业务,这对订单业务非常不友好,对于这种情况,就需要Event异步解耦。

示例代码:

csharp 复制代码
// 支付成功发布事件
@Component
public class PayEventPublishService {
​
    @Autowired
    private ApplicationEventPublisher publisher;
​
    public void publish() {
        // 发布事件
        System.out.println("支付成功");
        // 支付信息
        PayEvent event = new PayEvent("34", 4,  true);
        publisher.publishEvent(event);
        System.out.println("修改订单状态");
    }
}
​
// 记账监听
@Component
public class BillListenerService {
​
    @EventListener
    public void BillListen(PayEvent payEvent) {
        // 记账逻辑
        System.out.println("开始记账,金额扣减"+ payEvent.getMoney());
    }
}
​
// 仓库监听
@Component
public class WarehouseListenerService {
​
    @EventListener
    public void WarehouseListen(PayEvent payEvent) {
        // 仓库扣减逻辑
        System.out.println("仓库扣减"+ payEvent.getCount());
    }
}

4. 使用 Spring Data JPA 进行数据访问

Spring Data JPA 是 Spring 框架的一部分,它提供了简化数据访问的方式,可以轻松地与数据库进行交互。

示例代码:

typescript 复制代码
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByLastName(String lastName);
}
​
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
​
    public List<User> findByLastName(String lastName) {
        return userRepository.findByLastName(lastName);
    }
}

结语

通过以上示例,我们详细解析了 Spring 在业务中的常见应用场景和用法,包括使用 IoC 实现策略模式、AOP 实现日志切面、监听器实现业务异步解耦,以及使用 Spring Data JPA 进行数据访问等。这些功能和组件能够帮助我们简化开发、提高代码质量和可维护性,是 Java 开发中不可或缺的利器。

希望本文能够帮助读者更好地理解和应用 Spring 框架,在实际项目中发挥其最大的作用。

相关推荐
大阳光男孩37 分钟前
Spring Boot 整合 Debezium 实现 MySQL 增量数据监听(嵌入式版)
spring boot·后端·mysql
皮皮林5511 小时前
ThreadLocal 不香了?ScopedValue才是王道?
后端
X-⃢_⃢-X2 小时前
一、第一阶段:认识 Spring Boot
java·spring boot·后端
前端工作日常2 小时前
我学习到的Java程序的生命周期
java·后端
AskHarries3 小时前
GitHub 登录配置流程
后端
Sinclair6 小时前
安企CMS的安装-常见安装问题排查
运维·后端
Sinclair6 小时前
安企CMS的安装-安装引导与初始化
运维·后端
Sinclair6 小时前
安企CMS的安装-源码编译安装
运维·后端·go
Sinclair6 小时前
安企CMS的安装-同一服务器多站点部署
运维·后端
Sinclair6 小时前
安企CMS的安装-服务器手动部署
运维·后端