SpringAOP應用(AspectJ)

任務需求

系統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聲明瞭消息服務,則執行發送消息的處理,否則只執行原操作。

發佈了82 篇原創文章 · 獲贊 10 · 訪問量 49萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章