掘金 后端 ( ) • 2024-05-08 16:35

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


引言

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


1. 使用 IoC 实现策略模式

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

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

// 定义策略接口
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去发现使用该注解的类或方法,增加额外的逻辑,如参数校验,缓存,日志等。

参数校验

代码示例:

// 定义一个注解作用于方法
@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设置缓存过期时间。

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

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

@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发现,使用前置通知和返回通知处理日志信息。代码示例:

@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异步解耦。

示例代码:

// 支付成功发布事件
@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 框架的一部分,它提供了简化数据访问的方式,可以轻松地与数据库进行交互。

示例代码:

@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 框架,在实际项目中发挥其最大的作用。