网上一大堆 Spring AOP 切面打日志的方法,笔者使用注解切入,不再赘述。
笔者在抽离 “同步更新缓存” 的切面时,遇到各种困难,最终实践找到一个比较简单的方法,来实现任意自定义方法的切入。
首先,AOP 的底层原理是动态代理,也就是 Spring 托管的 Bean 容器。至少具备以下条件:
- 它是个 Bean (创建Bean的六种方式),一般 Bean 都是 public 修饰的(动态代理会忽视 private) 的方法。
那么,怎么达成条件呢? 以下方法二选一皆可。
一、将它升级为新 Bean
- Service 接口添加方法
public interface CartService {
/**
* 合并购物车:因为只有 Bean 可以被 AOP 代理
*
* @param tempCartItems 离线购物车
* @param oauthCartItems 登录购物车
* @return {@link List}<{@link CartItem}>
*/
List<CartItem> addAllRedisCartItemsByKey(List<CartItem> tempCartItems, List<CartItem> oauthCartItems);
}
- @Service 实现类注入自身,然后调用自身服务
cartService.addAllRedisCartItemsByKey(tempCartItems, oauthCartItems);
(写 this 或 不写前缀,会当作内部方法,导致托管无效了)
@Service
public class CartServiceImpl implements CartService {
@Autowired
private CartService cartService;
@Override
public Cart fetchTotalCartItems() {
Cart cart = new Cart();
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
Long userId = userInfoTo.getUserId();
String tempCartKey = CART_PREFIX + userInfoTo.getUserKey();
List<CartItem> tempCartItems = collectRedisCartItems(tempCartKey);
if (Objects.isNull(userId)) {
cart.setItems(tempCartItems);
return cart;
}
return addAllCartItems(cart, userId, tempCartItems);
}
private Cart addAllCartItems(Cart cart, Long userId, List<CartItem> tempCartItems) {
String oauthCartKey = CART_PREFIX + userId;
List<CartItem> oauthCartItems = collectRedisCartItems(oauthCartKey);
if (CollectionUtils.isNotEmpty(tempCartItems)) {
oauthCartItems = cartService.addAllRedisCartItemsByKey(tempCartItems, oauthCartItems);
}
cart.setItems(oauthCartItems);
return cart;
}
- 使用注解切入自定义的方法(只有此处需要同步更新远程的 Redis 缓存)
/**
* 合并两个购物车单品:离线+在线(同类合并和更新数量)
* 离线购物车 =
* 重复:更新数量
* 未重复:加入在线购物车
* <p>
* 离线购物车转成集合,去重(更新并删除重复)后,更新Redis
*
* @param tempCartItems 离线购物车
* @param oauthCartItems 登录用户购物车的单品
* @return {@link List}<{@link CartItem}>
*/
@Override
@PutRedis("合并购物车")
@DeleteRedis("合并后删除离线购物车缓存")
public List<CartItem> addAllRedisCartItemsByKey(List<CartItem> tempCartItems, List<CartItem> oauthCartItems) {
Map<Long, CartItem> tempCartItemsMap = tempCartItems.stream().collect(
Collectors.toMap(CartItem::getSkuId, Function.identity()));
List<CartItem> newOauthCartItems = oauthCartItems.stream().map(v -> {
Long skuId = v.getSkuId();
if (tempCartItemsMap.containsKey(skuId)) {
int updateCount = tempCartItemsMap.get(skuId).getCount() + v.getCount();
v.setCount(updateCount);
tempCartItemsMap.remove(skuId);
}
return v;
}).collect(Collectors.toList());
List<CartItem> newTempCartItems = tempCartItemsMap.entrySet().stream().map(Map.Entry::getValue)
.collect(Collectors.toList());
newOauthCartItems.addAll(newTempCartItems);
return newOauthCartItems;
}
}
二、使用老 Bean 从业务顶部切入
- 侵入实体类,添加类似“锁”的属性
@ToString
public class Cart {
/**
* 离线 + 登录 = 合并购物车
*/
@Getter
@Setter
private boolean allItemsCombined = false;
}
- 侵入业务方法,开关“锁”,
cart.setAllItemsCombined(true);
private Cart addAllCartItems(Cart cart, Long userId, List<CartItem> tempCartItems) {
String oauthCartKey = CART_PREFIX + userId;
List<CartItem> oauthCartItems = collectRedisCartItems(oauthCartKey);
if (CollectionUtils.isNotEmpty(tempCartItems)) {
oauthCartItems = cartService.addAllRedisCartItemsByKey(tempCartItems, oauthCartItems);
cart.setAllItemsCombined(true);
}
cart.setItems(oauthCartItems);
return cart;
}
- 使用注解切入
cartService
调用的方法顶部
@PutRedis("合并购物车")
@DeleteRedis("合并后删除离线购物车缓存")
@Override
public Cart fetchTotalCartItems() {...}
- 在切面方法中,校验返回值或参数,分情况讨论和处理(但每次局部切面遇到,都要做很多无用的校验,所以推荐使用方法一)
/**
* 删除缓存
*
* @param point 切入点:拦截首位参数
* @param deleteRedis 删除缓存注解
* @param returnValue 返回值 Cart:清空离线购物车,其他(void):按照skuId删除
*/
@AfterReturning(value = "@annotation(deleteRedis)", returning = "returnValue")
public void deleteRedisCacheSync(JoinPoint point, Object returnValue, DeleteRedis deleteRedis) {
BoundHashOperations<String, Object, Object> ops = boundUserIdFirstRedisHashOps();
if (Objects.isNull(returnValue)) {
String skuId = point.getArgs()[0].toString();
ops.delete(skuId);
return;
}
Cart cart = (Cart) returnValue;
if (cart.isAllItemsCombined()) {
String tempCartKey = getTempCartKey();
redisTemplate.delete(tempCartKey);
}
}
以上。