任务需求
系统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声明了消息服务,则执行发送消息的处理,否则只执行原操作。