AOP技术使用非常广泛,在Spring体系中随处可见,如缓存、事务等。在Spring的bean中使用this引用,可能会导致AOP失效。
简单例子:
@Slf4j
@Component
public class SysUserCache {
@Autowired
private SysUserMapper sysUserMapper;
@Cacheable(cacheNames = "m1", key = "'sys_acc_user_map'")
public Map<String, SysUser> sysAccountUserMap() {
return sysUserMapper.selectByExample(new SysUserExample()).stream()
.collect(Collectors.toMap(p -> p.getAccount(), Function.identity(), (a, b) -> b));
}
public Map<Integer, SysUser> sysAccountIdUserMap() {
return this.sysAccountUserMap().entrySet().stream().map(p -> p.getValue())
.collect(Collectors.toMap(p -> p.getId(), Function.identity(), (a, b) -> b));
}
}
sysAccountUserMap方法从数据库中查到所有的SysUser并缓存起来,sysAccountIdUserMap方法中this引用并不会触发缓存的AOP增强,原因是this引用的对象并不是AOP增强后的代理对象,而是被代理目标实例,详细可参考《透过现象看原理:详解Spring中Bean的this调用导致AOP失效的原因》这篇文章的分析,当然,这篇文章也给出了几种解决办法大家可研究下。
回到问题本身,代理对象中this引用不是可触发AOP的对象,那我们要做的就是把这个this引用替换掉,要么让this引用的还是代理对象,要么这里用that引用(脑补:that=代理对象)。让this引用的还是代理对象对代码无侵入性,一般是全局的修改,同时也失去了一定的灵活性(比如需要引入自身不触发代理的场景)。
that引用,或者说self引用,目标是在bean中提供一个直接引用代理对象的属性或function。下面看看如何实现。
简单粗暴,继承大法
定义自引用的抽象类:
public class AbstractSelfRefBean<SELF extends AbstractSelfRefBean<SELF>> {
@Setter
private SELF self;
protected SELF self() {
return self;
}
}
该抽象类有一个引用自身的属性,只需要在合适的时机将代理对象set进去就可以,引用时将this替换成self()即可:
public Map<Integer, SysUser> sysAccountIdUserMap() {
return self().sysAccountUserMap().entrySet().stream().map(p -> p.getValue())
.collect(Collectors.toMap(p -> p.getId(), Function.identity(), (a, b) -> b));
}
这个set进去的时机小伙伴们可能马上想到BeanPostProcessor,参考如下:
public class InjectSelfRefBeanProcessor implements BeanPostProcessor, ApplicationContextAware {
@Setter
private ApplicationContext applicationContext;
/**
* 继承{@link AbstractSelfRefBean}的子类使用了嵌套代理,需要特殊处理
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractSelfRefBean) {
if (AopUtils.isAopProxy(bean)) {
// 如果当前对象是AOP代理对象,直接注入
((AbstractSelfRefBean) bean).setSelf((AbstractSelfRefBean) bean);
} else {
// 如果当前对象不是AOP代理,则通过applicationContext.getBean(beanName)获取代理对象并注入
// 此种方式不适合解决prototype Bean的代理对象注入
((AbstractSelfRefBean) bean).setSelf((AbstractSelfRefBean) applicationContext.getBean(beanName));
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
那么只要定义了一个InjectSelfRefBeanProcessor,继承AbstractSelfRefBean的bean在初始化前会去检查其类型,如果是AOP代理对象,则self()直接引用自身,如果不是AOP代理对象,则引用ApplicationContext中的代理对象。使用时也很简单了
@Slf4j
@Component
public class SysUserCache extends AbstractSelfRefBean<SysUserCache> {
@Autowired
private SysUserMapper sysUserMapper;
@Cacheable(cacheNames = "m1", key = "'sys_acc_user_map'")
public Map<String, SysUser> sysAccountUserMap() {
return sysUserMapper.selectByExample(new SysUserExample()).stream()
.collect(Collectors.toMap(p -> p.getAccount(), Function.identity(), (a, b) -> b));
}
public Map<Integer, SysUser> sysAccountIdUserMap() {
return self().sysAccountUserMap().entrySet().stream().map(p -> p.getValue())
.collect(Collectors.toMap(p -> p.getId(), Function.identity(), (a, b) -> b));
}
}
简洁有效,实现大法
这样做可能不那么简洁,并且由于java的单继承规则限制,感觉不够完美,是否可以实现接口大法呢?我们继续尝试。
首先,依然是定义一个提供引用自身函数的方法。当然,最好给方法默认实现,这样bean定义实现该方法时无须再实现了。
public interface SelfRefBean<SELF extends SelfRefBean<SELF>> {
default SELF self() {
return Optional.ofNullable((SELF) SelfRefBeanDelegator.lookup(this)).orElse((SELF) this);
}
}
自引用的获取是委托第三方拿到的,这个第三方解决的问题就是把继承大法里自引用对象保存的位置挪到委托方中去,跟具体的bean分开。
@Data
@AllArgsConstructor
public class SelfRefBeanDelegator {
private static final Map<SelfRefBean, SelfRefBeanDelegator> BEAN_CACHE = new HashMap<>();
private String beanName;
private SelfRefBean proxyBean;
public static SelfRefBean lookup(SelfRefBean bean) {
return AopUtils.isAopProxy(bean) ? bean
: BEAN_CACHE.get(bean) == null ? null : BEAN_CACHE.get(bean).getProxyBean();
}
public static void register(String beanName, SelfRefBean bean, SelfRefBean proxyBean) {
BEAN_CACHE.put(bean, new SelfRefBeanDelegator(beanName, proxyBean));
}
}
代理对象注册到委托方SelfRefBeanDelegator依然使用BeanPostProcessor
public class InjectSelfRefBeanProcessor implements BeanPostProcessor, ApplicationContextAware {
@Setter
private ApplicationContext applicationContext;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof SelfRefBean) {
if (AopUtils.isAopProxy(bean)) {
SelfRefBeanDelegator.register(beanName, ((SelfRefBean) bean).self(), (SelfRefBean) bean);
} else {
SelfRefBeanDelegator.register(beanName, (SelfRefBean) bean,
(SelfRefBean) applicationContext.getBean(beanName));
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
其实就是在SelfRefBeanDelegator中记录目标对象跟代理对象的映射关系,这里有个小技巧是当前对象为代理对象时获取其目标对象,会先去缓存里查找,找不到说明当前对象还未创建委托方,那么调用self()返回this就拿到了代理对象。
使用时让bean实现SelfRefBean接口,代码中需要使用自身代理对象时用self()替代this即可。
@Slf4j
@Component
public class SysUserCache implements SelfRefBean<SysUserCache> {
@Autowired
private SysUserMapper sysUserMapper;
@Cacheable(cacheNames = "m1", key = "'sys_acc_user_map'")
public Map<String, SysUser> sysAccountUserMap() {
return sysUserMapper.selectByExample(new SysUserExample()).stream()
.collect(Collectors.toMap(p -> p.getAccount(), Function.identity(), (a, b) -> b));
}
public Map<Integer, SysUser> sysAccountIdUserMap() {
return self().sysAccountUserMap().entrySet().stream().map(p -> p.getValue())
.collect(Collectors.toMap(p -> p.getId(), Function.identity(), (a, b) -> b));
}
}