任務需求
系統A的數據庫中定義了一張document表,用來存放文檔的基本信息,當用戶對錶數據執行增刪改操作時,需要發送消息給系統B,通知其執行相應處理,消息內容包括文檔變動類型和文檔數據信息。需求實現
我們能想到的最簡單的處理方式如下:
修改數據和發送消息本是兩個獨立的功能,doAction方法卻將其耦合在了一起,方法的處理暫時不存在任何問題,然而隨着時間的推移,需求的變動,如果想要禁用消息就需要對業務層的代碼進行修改,將sendMessage方法註釋掉。
能否通過一種更爲優雅的方式來避免硬編碼情況的發生?
在上述處理方式中引入了服務容器的概念,消息服務相當於功能切面,而Spring框架相當於功能切面的容器。
doAction方法通過標籤的方式來聲明對消息服務的依賴,如果容器中含有消息服務,則在修改數據的同時觸發消息,如果沒有,則只完成修改數據的處理操作。
這樣處理的好處是服務之間的耦合度變的更加鬆散,可通過服務插拔的處理方式來實現業務需求的變動,如果想要禁用消息,只需要將消息切面從Spring容器中移除即可,而不用修改任何代碼。
實現細節
首先定義出消息服務切面切面由切入點和通知構成,AspectJ中通過@Aspect標籤來聲明
@Aspect
public class SendMessageAdvice {
private static final Logger LOG = LoggerFactory.getLogger(SendMessageAdvice.class);
@Pointcut("@annotation(org.chen.aopdemo.SendMessage)")
public void send() {}
@Around("send()")
public void doSendMessage(final ProceedingJoinPoint pjp) throws Throwable {
pjp.proceed();//執行被攔截的方法
//獲取所攔截的方法名
MethodSignature msig=(MethodSignature) pjp.getSignature();
String name = msig.getName();
Class<?>[] parameters = msig.getParameterTypes();
Object target = pjp.getTarget();
Method method = target.getClass().getMethod(name, parameters);//獲取攔截的method
//方法是否使用了泛型參數
if(method.isBridge()){
Class<?> erasedParam=null;
Class<?> targetParam=null;
Annotation[] annotations=target.getClass().getAnnotations();
first:for(Annotation annotation:annotations){
if(annotation instanceof BridgeMethodMappings){
BridgeMethodMappings mappings=(BridgeMethodMappings) annotation;
for(BridgeMethodMapping mapping:mappings.value()){
if(mapping.methodName().equals(name)){
erasedParam=mapping.erasedParamTypes()[0];
targetParam=mapping.targetParamTypes()[0];
break first;
}
}
}
}
//將泛型參數替換成指定參數
for(int i=0;i<parameters.length;i++){
if(parameters[i].equals(erasedParam)){
parameters[i]=targetParam;
}
}
//重新得到方法聲明
method = target.getClass().getMethod(name, parameters);//獲取攔截的method
}
SendMessage sendMsg=method.getAnnotation(SendMessage.class);//獲取方法所聲明的SendMessage標籤
SignalType singleType=sendMsg.single();//獲取標籤聲明的消息類型
Annotation[][] annotations=method.getParameterAnnotations();
Integer msgDataIndex=null;
//獲取含有MessageData標籤的方法參數索引
first:for(int i=0;i<annotations.length;i++){
Annotation[] paramAnnotation=annotations[i];
for(int j=0;j<paramAnnotation.length;j++){
if(paramAnnotation[j] instanceof MessageData){
MessageData msgData=(MessageData) paramAnnotation[j];
if(msgData.order()==0){
msgDataIndex=new Integer(i);
break first;
}
}
}
}
if(msgDataIndex!=null){
Serializable obj=(Serializable) pjp.getArgs()[msgDataIndex];//獲取方法參數值
doSendMessage(singleType,obj);//執行發送消息操作,傳遞消息類型和消息數據
LOG.info("發送消息成功,消息類型:"+singleType.getOp()+",消息數據:"+obj);
}
}
}
@Pointcut標籤聲明瞭消息服務的切入點,如果方法聲明瞭SendMessage標籤則攔截該方法執行發送消息的處理。@Around標籤聲明瞭通知,方法中描述了發送消息的具體操作。
然後將消息服務切面注入到Spring容器中
在Spring配置文件中添加如下配置
<aop:aspectj-autoproxy />
<bean id="sendMessage" class="cn.com.gei.krp.ecm.core.index.jms.SendMessageAdvice">
<property name="jmsurl" value="tcp://user-df29b9c8dc:61616?jms.useAsyncSend=true" />
</bean>
最後在DocumentDao的相關方法中對消息服務聲明依賴(通過SendMessage標籤)@BridgeMethodMappings({
@BridgeMethodMapping(methodName="delete",erasedParamTypes={Serializable.class},targetParamTypes={String.class}),
@BridgeMethodMapping(methodName="update",erasedParamTypes={Object.class},targetParamTypes={Document.class}),
@BridgeMethodMapping(methodName="insert",erasedParamTypes={Object.class},targetParamTypes={Document.class}),
})
public class DocumentDao extends
SimpleHibernateDao<Document, String> implements IDocumentDao{
@Override
@SendMessage(type=MessageType.deleteDocument)
public void delete(@MessageData String id) {
super.delete(id);
}
@Override
@SendMessage(type=MessageType.updateDocument)//聲明消息服務依賴
public void update(@MessageData Document entity) {
super.update(entity);
}
@Override
@SendMessage(type=MessageType.addDocument)
public void insert(@MessageData Document entity) {
super.insert(entity);
}
}
這樣,在執行document的增刪改操作時,如果Spring聲明瞭消息服務,則執行發送消息的處理,否則只執行原操作。