缘由
由于要对数据库的部分数据做缓存,并且没有现成的缓存与数据库的一致性的保障机制,所以只能在DAO对数据库操作的同时更新缓存。
为了避免对原有代码的侵入,决定采用spring的aop拦截DAO对数据库的操作然后更新缓存。
分析
尝试
一开始,我实现了如下的代码,对删除进行拦截:
@AfterReturning(
pointcut = "execution(public * dao.AccountDao+.deleteX(..))",
returning = "affectedRowCount"
)
public void delete(JoinPoint joinPoint, int affectedRowCount) {
if (affectedRowCount == 1) {
xCache.delete((Integer) joinPoint.getArgs()[0]);
}
}
但是测试发现,拦截并没有成功。
深入
经检查,spring的配置没有问题,aop的语法也是正确的。那为什么拦截不到呢?
于是怀疑跟AccountDao这个interface有关:
public interface AccountDao extends GenericDao {
@DAOAction(action = DAOActionType.UPDATE)
public int delete(@DAOParam("accountId") int accountId);
//...
}
仔细查看源码,发现这个interface其实并没有实现类!也就是说,这个interface或者他内部的方法是动态生成的。
查看这个bean的定义如下:
<bean id="accountDao" parent="parentDao">
<property name="proxyInterfaces"
value="dao.AccountDao" />
<property name="target">
<bean parent="daoRealizeTarget">
<constructor-arg value="Account" />
</bean>
</property>
</bean>
<bean id="parentDao" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
<property name="interceptorNames">
<list>
<value>daoRealizerAdvisor</value>
</list>
</property>
</bean>
<!-- The introduce interceptor for all Dao -->
<bean id="daoRealizer" class="dao.DAORealizer" />
<!-- The advisor for inject interceptor -->
<bean id="daoRealizerAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="daoRealizer" />
<property name="expression" value="execution(* dao..*.*(..)) and !execution(* xx.dao..*.*(..))" />
</bean>
定义比较复杂,我们一步步来看:
首先,他是有一个父类dao的基础上创建的
<bean id="accountDao" parent="parentDao">
而这个父类是由spring的ProxyFactoryBean创建的,需要注意的是:像其它的FactoryBean实现一样,ProxyFactoryBean引入了一个间接层。如果你定义一个名为parentDao的ProxyFactoryBean, 引用parentDao的对象看到的将不是ProxyFactoryBean实例本身,而是一个ProxyFactoryBean实现里getObject() 方法所创建的对象。 这个方法将创建一个AOP代理,它包装了一个目标对象。
<bean id="parentDao" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
也就是说,accountDao其实是AOP代理。那代理做了什么呢?
他给Dao添加了名为daoRealizerAdvisor的拦截器,拦截所有对Dao的调用。而advice便是DAORealizer这个类。
<bean id="daoRealizer" class="x.dao.DAORealizer" />
<!-- The advisor for inject interceptor -->
<bean id="daoAutoRealizerAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="daoAutoRealizer" />
<property name="expression" value="execution(* com.dianping..dao..*.*(..)) and !execution(* com.dianping.avatar..dao..*.*(..))" />
</bean>
--------------
public class DAORealizer implements IntroductionInterceptor {
@Override
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
// some pre-condition check
DAOMethod daoMethod = DAOUtils
.createDAOMethod(methodInvocation.getMethod(), methodInvocation.getArguments());
final GenericDao genericDao = (GenericDao) methodInvocation.getThis();
if (daoMethod.actionType == DAOActionType.LOAD) {
return genericDao.executeLoad(daoMethod);
}
if (daoMethod.actionType == DAOActionType.QUERY) {
return genericDao.executeQuery(daoMethod);
}
// ... other different type
return methodInvocation.proceed();
}
}
看到这里,真相大白,原来我们对于这个Dao的调用本来就已经是被spring的interceptor1给拦截了, DaoRealizer这个Advisor在我们的aop advice之前(怎么知道在我们的aop之前的?见下一段解释),而且他直接调用的便是return genericDao.executeXXX(daoMethod);
,就不会调用之后的proceed方法了2.
就是如下代码片段,在此处设置断点,可以看到对某个join point的interceptors,并且interceptors是有顺序的,可以看到不同interceptor的先后关系
public class AdvisedSupport extends ProxyConfig implements Advised {
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
}
解决方案
方案一:拦截真正的方法
既然他最后调用的是GenericDao的执行方法,那我们便可以拦截这个方法,完成更新
@AfterReturning(
pointcut = "execution(public * x.dao.GenericDao.executeInsert(..))",
returning = "id"
)
public void insertId(JoinPoint joinPoint, int id) {
final DAOMethod daoMethod = (DAOMethod) joinPoint.getArgs()[0];
if (daoMethod.getName().equals("addAccount")) {
adAccountIDCache.insert(id);
}
}
但是,显然这样对该DAO的实现有很大的依赖性,所以并不是很理想
方案二:在我们的Dao上层添加proxy
通过配置spring的bean的id,使得调用方使用我们的proxy,然后我们再调用真正的dao。
这样子,我们便可以拦截我们自己的proxy,进行更新操作。
public class AccountDaoProxy implements AccountDao {
@Autowired
private AccountDao realAccountDao;
@Override
@DAOAction(action = DAOActionType.UPDATE)
public int delete(@DAOParam("accountId") int accountId) {
return accountDao.delete(accountId);
}
//。。。
}
<bean id="accountDao" class="dao.AccountDaoProxy"/>
<bean id="realAccountDao" parent="parentDao">
@AfterReturning(
pointcut = "execution(public * dao.AccountDaoProxy.delete(..))",
returning = "affectedRowCount"
)
public void delete(JoinPoint joinPoint, int affectedRowCount) {}
但是运行后发现,直接这样调用的还是不行!
分析后发现,虽然我们是在AdAccountDaoProxy上添加的aop拦截,但是我们调用的方法还是AccountDao的deleteAccount方法(调用方使用的是接口!),而所有的Dao方法都被DaoRealizer拦截了,所以我们的proxy方法还是不会执行(即,在获取Advisor的时候,还是会获取原先的DaoAutoRealizer,从而调用executeUpdate然后返回。。。)
方案三:为我们的Dao添加一个interceptor
spring官网有如下介绍:
A child bean definition inherits configuration data from a parent definition. The child definition can override some values, or add others, as needed.
所以我们可以覆盖父类的interceptor,添加我们的advisor:
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>daoRealizerAdvisor</value>
</list>
</property>
--------------
public class CacheUpdater implements IntroductionInterceptor {
private void deleteId(int innerAccountId, int affectedRowCount) {
if (affectedRowCount == 1) {
accountCache.delete(innerAccountId);
}
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
final Object proceed = methodInvocation.proceed();
final String methodName = methodInvocation.getMethod().getName();
if (methodName.equals("addAccount")) {
insertId((Integer) proceed);
} else if (methodName.equals("deleteAccount")) {
final Integer id = (Integer) methodInvocation.getArguments()[0];
deleteId(id, (Integer) proceed);
}
return proceed;
}
private void insertId(int innerAccountId) {
accountCache.insert(innerAccountId);
}
}
方案四:Order
说到底,一开始的aop不能成功执行都是因为avatar的aop在我的aop之前,并且他直接返回了,没有继续调用之后的interceptor,所以我们要在他前面添加一个interceptor。
搜索之后发现,aspectJ直接提供了注解来做这个事情,即order,方便快捷
@Aspect
@Order(1)
public class CacheUpdater {
@Autowired
private AccountCache accountCache;
@Around(value = "execution(public * dao.AccountDao+.addAccount(..))")
public Object insertId(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
proceed = pjp.proceed();
accountCache.insert((Integer) proceed);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
//。。。
}
Written with StackEdit.
Advice: action taken by an aspect at a particular join point. Different types of advice include “around,” “before” and “after” advice. (Advice types are discussed below.) Many AOP frameworks, including Spring, model an advice as an interceptor, maintaining a chain of interceptors around the join point
↩- interceptors的结构类似与责任链或者是servlet中的filter:可以想象一个调用链,调用proceed则进入下一个节点,在中间某一环return就直接返回了,之后的interceptor或是真正的实现,都没有机会被调用了!也就是说我们之后添加的advice是不会被调用的!! ↩
评论
发表评论