项目中使用json传递数据,定义了一套统一的格式,如下所示,但是实际上业务层面只需要处理data节点的数据,sign、signType参数由框架层面进行验签处理。项目中的web层使用了springmvc、resteasy框架,为了方便接收data节点的json数据,笔者扩展了springmvc的源码,添加了自定义的HandlerMethodArgumentResolver。
{
"sign":"xxx",
"signType":"xxx",
"data":{
"partnerId":"xxx",
......
}
}
springmvc部分源码分析
下面是springmvc处理请求的核心流程
先是根据HttpServletRequest遍历所有的HandlerMapping,常用的实现类有RequestMappingHandlerMapping、BeanNameUrlHandlerMapping,调用其getHandler方法获取HandlerExecutionChain,这个对象里面包括了我们熟悉的HandlerInteceptor拦截器,会在处理请求前、后、产生响应的时候被调用。然后,根据HandlerExecutionChain的Handler实例,获取HandlerAdapter,同样的,也是遍历List,如果HandlerAdapter.supports(handler)则返回,比如RequestMappingHandlerAdapter、HttpRequestHandlerAdapter。接下来,将请求交给HandlerAdapter处理,返回ModelAndView。
HanderAdapter是调用Controller的核心接口,由它负责处理请求。RequestMappingHandlerAdapter是HandlerAdapter的子类,它支持对HandlerMethod的处理,并进行参数处理、执行HandlerMethod,处理响应数据等。它包括了常见的参数解析器(HandlerMethodArgumentResolver),例如对@RequestBody、@Path的处理,大家可以看下它的实现类。另外,还有方法返回数据的处理类(HandlerMethodReturnValueHandler)。我们现在需要对入参进行处理,因此需要实现HandlerMethodArgumentResolver接口。如果对响应数据进行处理,需要实现HandlerMethodReturnValueHandler。
关键是怎么将自定义的HandlerMethodArgumentResolver添加到RequestMappingHandlerAdapter中?因为RequestMappingHandlerAdapter是受spring管理的类,如果是以xml配置springmvc的话,org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser在处理标签时会注册RequestMappingHandlerAdapter的bean定义。如果是以@EnableWebMvc注解配置springmvc的话,也会有这个类的定义。既然如此,搞个BeanPostProcessor应该是可以的。具体的代码,请笔者结合时序图进行阅读。
如何扩展
先自定义个注解,用来支持对json中的data节点进行处理
/**
* 用于扩展SpringMVC的参数解析功能,只读取json串中的data节点作为Controller方法入参,eg:
* public JsonResult pay( @RequestDataBody PayRequest request, Sign sign )
* @author huangxf
* @date 2017年4月10日
*/
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestDataBody {
boolean required() default true;
}
写个BeanPostProcessor实现postProcessBeforeInitialization方法,并且将这个bean交给spring管理即可。这样,我们在RequestMappingHandlerAdapter初始化之前,便可以添加自定义的参数解析器,如下所示。我的github提供的代码,支持SessionUser(接口)、Sign(接口)、@RequestDataBody的处理,相关的代码在net.dwade.plugins.spring.web这个包下面,github地址:https://github.com/huangxfchn/dwade/tree/master/framework-plugins
/**
* 使用{@link BeanPostProcessor}对SpringMVC进行扩展,
* 支持{@link RequestDataBody}、{@link Sign}、{@link SessionUser}、{@link CheckSign}、{@link SignResponseBody}
* @see RequestMappingHandlerAdapter
* @see BeanPostProcessor
* @author huangxf
* @date 2017年4月13日
*/
public class PaymentControllerSupport implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if ( bean instanceof RequestMappingHandlerAdapter ) {
RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter)bean;
registerArgumentsResolvers( adapter );
registerReturnValueHandlers( adapter );
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
/**
* 注册参数解析器
* @param adapter
* @return void
*/
private void registerArgumentsResolvers( RequestMappingHandlerAdapter adapter ) {
List<HandlerMethodArgumentResolver> resolvers = this.getCustomerArgumentResolvers( adapter );
if ( adapter.getCustomArgumentResolvers() == null ) {
adapter.setCustomArgumentResolvers( resolvers );
} else {
adapter.getCustomArgumentResolvers().addAll( resolvers );
}
}
/**
* 注册返回值处理器
* @param adapter
* @return void
*/
private void registerReturnValueHandlers( RequestMappingHandlerAdapter adapter ) {
List<HandlerMethodReturnValueHandler> resolvers = this.getCustomerReturnValueHandler( adapter );
if ( adapter.getCustomReturnValueHandlers() == null ) {
adapter.setCustomReturnValueHandlers( resolvers );
} else {
adapter.getCustomReturnValueHandlers().addAll( resolvers );
}
}
protected List<HandlerMethodArgumentResolver> getCustomerArgumentResolvers( RequestMappingHandlerAdapter adapter ) {
//处理method参数中SessionUser
HandlerMethodArgumentResolver sessionUserResolver = new SessionUserResolver();
applicationContext.getAutowireCapableBeanFactory().initializeBean( sessionUserResolver, SessionUserResolver.class.getName() );
//对请求参数进行处理,解析data节点、签名
HandlerMethodArgumentResolver requestDataResolver = new RequestDataConverterProcessor( adapter.getMessageConverters() );
applicationContext.getAutowireCapableBeanFactory().initializeBean( requestDataResolver, RequestDataConverterProcessor.class.getName() );
//处理验签
HandlerMethodArgumentResolver signResolver = new SignResponseProcessor( adapter.getMessageConverters() );
applicationContext.getAutowireCapableBeanFactory().initializeBean( signResolver, SignResponseProcessor.class.getName() );
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add( sessionUserResolver );
resolvers.add( requestDataResolver );
resolvers.add( signResolver );
return resolvers;
}
/**
* 获取返回参数处理的HandlerMethodReturnValueHandler实现类
* @param adapter
* @return List<HandlerMethodReturnValueHandler>
*/
protected List<HandlerMethodReturnValueHandler> getCustomerReturnValueHandler( RequestMappingHandlerAdapter adapter ) {
//处理签名、验签
HandlerMethodReturnValueHandler signHandler = new SignResponseProcessor( adapter.getMessageConverters() );
applicationContext.getAutowireCapableBeanFactory().initializeBean( signHandler, SignResponseProcessor.class.getName() );
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
handlers.add( signHandler );
return handlers;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}
如何使用
json报文如下,假设我需要将data节点,使用PayOffRequest对象接收,只需要添加@RequestDataBody注解即可
{
"sign":"xxx",
"signType":"xxx",
"data":{
"partnerId":"xxx",
"money":10000
}
}
Controller代码
@Controller
@RequestMapping( "/pay" )
public class PaymentController {
private final Logger logger = LoggerFactory.getLogger( PaymentController.class );
/**
* <code>@CheckSign</code>:需要验签,<code>@SignResponseBody</code>:响应的数据需要签名处理
* @param request
* @param user
* @return PaymentResponse<PayOffResponse>
*/
@RequestMapping(value="payoff", method=RequestMethod.POST)
public PaymentResponse<PayOffResponse> payOff( @RequestDataBody PayOffRequest request,
HttpServletRequest httpRequest, SessionUser user ) {
// your code...
}
}