源地址 http://neoremind.com/2016/02/springmvc%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B8%B8%E7%94%A8%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/前端
文章寫得很是好,轉過來學習下java
本文分兩部分,第一部分剖析SpringMVC的源代碼,看看一個請求響應是如何處理,第二部分主要介紹一些使用中的最佳實踐,這些best practices有些比較common,有些比較tricky,旨在展現一個框架的活力以及一些能在平常項目中可以應用的技巧,這些技巧的線索均可以在第一部分的代碼剖析中找到,因此讀讀源代碼對於使用好任何框架都是很是有幫助的,正所謂「知其然,還要知其因此然」。web
另外,本文中所涉及的Spring版本是3.1.2RELEASE。算法
SpringMVC一直活躍在後端MVC框架的最前沿,不少web系統都是構建在此之上。最多見就是編寫一個Controller,代碼片斷以下:spring
@RequestMapping(value = "/getTemplateInfo", method = RequestMethod.GET) @ResponseBody public JsonObject<?> getTemplateInfo(@RequestParam(value = "userId", required = true) int userId, @RequestParam(value = "groupType") int groupType) { // ... logic here } |
以該例子爲背景,先簡單剖析下SpringMVC源代碼看看它的HTTP請求響應模型是怎樣的,跟着流程走一遍。後端
衆所周知,SpringMVC是創建在Servlet基礎之上,通常來講配置全部的請求都由DispatcherServlet來處理,從web.xml的配置中就能夠看出來。設計模式
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> |
DispatcherServlet是整個框架的核心所在,它既是一個標準的HttpServlet,也是利用開放封閉原則(Open-Closed)進行設計的經典框架。一句英文歸納就是「對擴展開發,對修改封閉」:數組
A key design principle in Spring Web MVC and in Spring in general is the "Open for extension,closed for modification" principle.服務器
在DispatcherServlet中能夠明顯看到:mvc
1)類中全部的變量聲明,幾乎都以接口的形式給出,並無綁定在具體的實現類上。
舉例來講,SpringMVC利用IoC動態初始化HandlerAdapter實例,也就說在applicationContext.xml中配置的一切接口實現的bean,若是名稱match,框架實際都默默的注入到了DispatcherServlet。
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter"; private List handlerAdapters; |
2)使用模版方法模式,方便擴展。
所謂模板模式就是定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。SpringMVC在保證整個框架流程穩定的狀況下,預留不少口子,而這些口子都是所謂的模板方法,能夠自由指定,從而保證了靈活性,接下來的不少使用最佳實踐都是基於這種設計模式才能夠實現。
例如,下面的代碼中doResolveException(..)就是一個口子,子類方法doResolveException(..)能夠定義具體如何處理異常。
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { if (shouldApplyTo(request, handler)) { logException(ex, request); prepareResponse(ex, response); return doResolveException(request, response, handler, ex); } else { return null; } } protected abstract ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); |
3)良好的抽象設計,是整個框架變得很是靈活。
舉例來講,在doDispatch(HttpServletRequest request, HttpServletResponse response)方法中有一段流程處理,大體能夠看出是獲取全部的攔截器,遍歷之,調用preHandle進行前置處理。
// Apply preHandle methods of registered interceptors. HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } |
而全部的攔截器都是HandlerInterceptor接口的實現,框架充分使用接口來回調這些開發人員指定的攔截器,這就是所謂的口子。
public interface HandlerInterceptor { boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; } |
帶着這些設計的思想,下面,真正進入請求響應處理的剖析。
整個流程能夠被大體描述爲:
一個http請求到達服務器,被DispatcherServlet接收。DispatcherServlet將請求委派給合適的處理器Controller,此時處理控制權到達Controller對象。Controller內部完成請求的數據模型的建立和業務邏輯的處理,而後再將填充了數據後的模型即model和控制權一併交還給DispatcherServlet,委派DispatcherServlet來渲染響應。DispatcherServlet再將這些數據和適當的數據模版視圖結合,向Response輸出響應。
這個流程以下圖所示:
圍繞DispatcherServlet的流程處理以下圖:
UML序列圖以下:
具體剖析DispatcherServlet,首先,客戶端發起請求,假如是一個GET請求,會由doGet(HttpServletRequest request, HttpServletResponse response)來處理,內部調用
void processRequest(HttpServletRequest request, HttpServletResponse response)。
在processRequest這一階段主要就是調用void doDispatch(HttpServletRequest request, HttpServletResponse response)方法。
在doDispatch主要有一下幾步操做。
(1)調用DispatcherServlet#getHandler(HttpServletRequest request)方法返回一個HandlerExecutionChain對象。
內部實現以下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; } |
首先是遍歷初始化好的HandlerMapping,具體查看HandlerMapping實現類,例如框架提供的最經常使用的實現RequestMappingHandlerMapping,先看下HandlerMapping是如何初始化的,下面代碼從AbstractHandlerMethodMapping中摘取,描述了其過程:
// 在Spring容器中初始化 public void afterPropertiesSet() { initHandlerMethods(); } // 初始化HandlerMethod protected void initHandlerMethods() { String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils .beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext() .getBeanNamesForType(Object.class)); for (String beanName : beanNames) { if (isHandler(getApplicationContext().getType(beanName))) { detectHandlerMethods(beanName); } } handlerMethodsInitialized(getHandlerMethods()); } // 看某個bean是不是controller protected boolean isHandler(Class<?> beanType) { return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) || (AnnotationUtils .findAnnotation(beanType, RequestMapping.class) != null)); } // 獲取某個controller下全部的Method,作url path->Method的簡單關聯 protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String) ? getApplicationContext().getType((String) handler) : handler.getClass(); final Class<?> userType = ClassUtils.getUserClass(handlerType); Set methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { public boolean matches(Method method) { return getMappingForMethod(method, userType) != null; } }); for (Method method : methods) { T mapping = getMappingForMethod(method, userType); registerHandlerMethod(handler, method, mapping); } } |
HandlerMapping通常是在applicationContext.xml中定義的,在Spring啓動時候就會注入到DispatcherServlet中,它的初始化方式主要依賴於AbstractHandlerMethodMapping這個抽象類,利用initHandlerMethods(..)方法獲取全部Spring容器託管的bean,而後調用isHandler(..)看是不是@Controller註解修飾的bean,以後調用detectHandlerMethods(..)嘗試去解析bean中的方法,也就是去搜索@RequestMapping註解修飾的方法,將前端請求的url path,例如「/report/query」和具體的Method來作關聯映射,例如一個HandlerMapping內含的屬性以下,將前端的「/portal/ad/getTemplateInfo」與SiConfController.getTemplateInfo(int,int)方法相綁定。以下所示:
[/portal/ad/getTemplateInfo],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}=public com.baidu.beidou.ui.web.common.vo.JsonObject<?>com.baidu.beidou.ui.web.portal.ad.controller.SiConfController.getTemplateInfo(int,int)] |
完成全部的搜索bean搜索後,調用registerHandlerMethod(..)將Method構造爲HandlerMethod,添加到HandlerMapping內含的屬性列表中:
private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>(); |
保存,這裏的T是泛型的,具體存放的最簡單就是url path信息。
繼續返回到DispatcherServlet#getHandler(HttpServletRequest request)方法中,遍歷上面講到的HandlerMapping,調用hm.getHandler(request)方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } return getHandlerExecutionChain(handler, request); } protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain) ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler); chain.addInterceptors(getAdaptedInterceptors()); String lookupPath = urlPathHelper.getLookupPathForRequest(request); for (MappedInterceptor mappedInterceptor : mappedInterceptors) { if (mappedInterceptor.matches(lookupPath, pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } return chain; } |
內部調用父類的AbstractHandlerMapping#getHandlerInternal(HttpServletRequest request),也就是說根據url path返回一個具體的HandlerMethod,而後調用getHandlerExecutionChain構形成一個HandlerExecutionChain,能夠看到就是在這個時候將全部根據xml的配置將攔截器添加到HandlerExecutionChain中的,這裏使用到了職責鏈模式。
(2)繼續回到主流程,調用DispatcherServlet#getHandlerAdapter(Object handler),代碼以下,
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } |
這裏會遍歷全部配置在Spring容器已經配好的handlerAdapters,調用supports(..)方法,獲得第一個返回爲true的HandlerAdapter,這裏的參數實際上就是上面提到的就是HandlerMethod。 supports主要就是驗證某個HandlerMethod上定義的參數、返回值解析,是否能由該handlerAdapter處理。
HandlerAdapter最主要的方法就是處理http請求,在下面會更詳細的講解。
public interface HandlerAdapter { ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; } |
(3) 開始進行攔截器處理,代碼以下:
// Apply preHandle methods of registered interceptors. HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } |
首先獲取全部的攔截器,而後依次遍歷調用HandlerExecutionChain#applyPreHandle(HttpServletRequest request, HttpServletResponse response)開始應用攔截器,一個一個調用其preHandle方法,若是有錯誤,直接退出調用afterCompletion方法,返回false。
(4)接着調用HandlerAdapter.handle(…)獲得一個ModelAndView對象,裏面封裝了數據對象及具體的View對象。
具體實現須要查看HandlerAdapter實現類。例如RequestMappingHandlerAdapter,如下代碼即從該類中截取。
private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); requestMappingMethod.invokeAndHandle(webRequest, mavContainer); // ... return ModelAndView } |
首先,getDataBinderFactory(..)獲取全部指定Controller里加入的@InitBinder註解,自定義作數據轉換用,以後調用getModelFactory獲取一個最終生成model的工廠,而後構造ServletInvocableHandlerMethod方法,重點在於ServletInvocableHandlerMethod#invokeAndHandle(webRequest, mavContainer)方法,截取內部的實現以下:
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); Object returnValue = invoke(args); return returnValue; } private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (argumentResolvers.supportsParameter(parameter)) { try { args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); continue; } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); } throw ex; } } if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; } |
依次遍歷目標調用方法上面的參數,嘗試從請求中解析參數與值而且作映射與bind,能夠看到這裏面argumentResolvers是核心,這個口子用於將前端請求與Controller上定義的參數類型相綁定,能夠天然想到這個抽象的設計,能夠給予框架使用者不少的靈活選擇。
(5)而後調用HandlerExecutionChain#applyPostHandle(…)再次應用攔截器,調用其postHandle方法。HandlerExecutionChain#applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception { if (getInterceptors() == null) { return; } for (int i = getInterceptors().length - 1; i >= 0; i--) { HandlerInterceptor interceptor = getInterceptors()[i]; interceptor.postHandle(request, response, this.handler, mv); } } |
(6)最後一步processDispatchResult處理結果,相應給客戶端。
在processDispatchResult首先調用了render(mv, request, response)方法。
最後是HandlerExecutionChain#triggerAfterCompletion(..),調用攔截器的afterCompletion方法。攔截器處理流程至此結束。
至此一個請求響應過程結束。
一般狀況下,框架能夠很好的處理前端傳遞k1=v2&k2=v2形式的POST data和GET請求參數,並將其轉換、映射爲Controller裏method的args[]類型,可是在某些狀況下,咱們有不少自定義的需求,例如對於字符串yyyyMMDD轉換爲Date對象,這時候自定義DataBinder就很是有用了,在上面源碼剖析的第(4)點介紹過。
一個更加trick的需求是,前端傳遞兩種cases的urls參數:
urls= http://c.admaster.com.cn/c/a25774,b200663567,c3353,i0,m101,h&urls=http://www.baidu.com urls= http://c.admaster.com.cn/c/a25774,b200663567,c3353,i0,m101,h
對於第一種,後端接收到的urls.size()=2,符合預期,而對於第二種,後端接收的urls.size()=5,不是預期的urls.size()=1,緣由就是SpringMVC進行參數映射綁定時,默認會自動把按照逗號分隔的參數映射成數組或者list的元素。對這個問題,一樣可使用WebDataBinder解決,解決代碼以下,只須要在Controller里加入一個@InitBinder修飾的方法,去在binder裏面加入自定義的參數解析方法便可。
@RequestMapping(value = "/getUrls", method = RequestMethod.GET) @ResponseBody public JsonObject<?> getUrls(@RequestParam(value = "urls") List urls) { JsonObject<?> result = JsonObject.create(); System.out.println(urls); result.addData("urls", urls); return result; } @InitBinder public void dataBinder(WebDataBinder binder) { PropertyEditor urlEditor = new PropertyEditorSupport() { @Override public void setValue(Object value) throws IllegalArgumentException { if (value instanceof List) { super.setValue(value); } else if (value.getClass().isArray() && value instanceof String[]) { super.setValue(Lists.newArrayList((String[]) value)); } } @Override public void setAsText(String text) throws java.lang.IllegalArgumentException { if (text instanceof String) { setValue(Lists.newArrayList(text)); return; } throw new IllegalArgumentException(text); } }; binder.registerCustomEditor(List.class, urlEditor); } |
一般狀況下,對於參數key的解析、映射,框架會幫助咱們完成到對象的綁定,可是在某些遺留系統中,前端傳遞的參數與後端Form表單定義的命名不會相同,例如在某些系統中參數爲qp.page=1&qp.pageSize=50,然後端的Form表單類屬性命名不可能帶有點號,這時候咱們能夠自定義一個ArgumentResolver來本身設置參數對象。
例如,咱們的query方法簽名以下,QueryParamForm中的屬性名稱爲page、pageSize:
@RequestMapping("/dtList") @ResponseBody public JsonObject<genderviewitem> query(@Qp QueryParamForm form) { ResultBundle<genderviewitem> res = reportService.queryGenderReport(toQP(form)); return toResponse(res, form); }</genderviewitem></genderviewitem> |
Qp是一個註解:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Qp { } |
在handlerAdapter中自定義customArgumentResolvers:
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="customArgumentResolvers"> <util:list> <ref bean="reportArgumentResolver" /> </util:list> </property> </bean> |
ArgumentResolver的實現以下,只須要覆蓋兩個方法便可,在上面源碼剖析(4)中介紹過對於參數的解析介紹過。在這裏省略了QueryParamFormBuilder類,這個類主要就是去webRequest中主動取"qp.page"與"qp.pageSize"參數的值,利用反射去動態的set到一個空QueryParamForm對象的屬性中。
@Component public class ReportArgumentResolver implements HandlerMethodArgumentResolver { @Resource private QueryParamFormBuilder formBuilder; @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(Qp.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (parameter.getParameterType() == QueryParamForm.class) { return formBuilder.buildForm(webRequest); } return WebArgumentResolver.UNRESOLVED; } } |
以上面那個例子爲背景,若是要作全局的參數校驗,不必在每一個方法中主動寫方法,能夠利用AspectJ與Spring的集成,編入指定類到方法上的AOP切面,統一來作驗證。詳細代碼以下:
@Component @Aspect public class ReportQueryParamInterceptor { private static final Logger LOG = LoggerFactory.getLogger(ReportQueryParamInterceptor.class); @Around("execution(* com.baidu.beidou.ui.web.portal.report.controller.*ReportController.query*(..))") public Object validate4Query(ProceedingJoinPoint pjp) throws Throwable { MethodSignature methodSignature = getMethodSignature(pjp); Object[] args = pjp.getArgs(); if (args == null || args.length < 1 || !(args[0] instanceof QueryParamForm)) { LOG.warn("Request param is null or not instanceof QueryParamForm! " + args); throw new IllegalArgumentException("Request param error which should not happen!"); } QueryParamForm form = (QueryParamForm) args[0]; JsonObject response = (JsonObject) (methodSignature.getReturnType().newInstance()); validateAndPrepareQueryParamForm(response, form); if (response.getStatus() != GlobalResponseStatusMsg.OK.getCode()) { return response; } return pjp.proceed(); } } |
一般狀況下,一個web系統,不該該像外部暴露過多的內部異常細節,那麼咱們能夠覆蓋掉SpringMVC提供的默認異常處理handler,定義本身的GlobalExceptionHandler,這裏面爲了覆蓋掉默認的handler,須要實現Ordered,而且賦值order爲Ordered.HIGHEST_PRECEDENCE。
在配置文件中使用本身的handler。
<bean id="exceptionHandler" class="com.baidu.beidou.ui.web.common.handler.GlobalExceptionHandler"> </bean> |
resolveException(..)方法內,能夠針對各類異常信息,去返回給前端不一樣的信息,包括錯誤返回碼等等。
public class GlobalExceptionHandler implements HandlerExceptionResolver, ApplicationContextAware, Ordered { protected ApplicationContext context; /** * 默認HandlerExceptionResolver優先級,設置爲最高,用於覆蓋系統默認的異常處理器 */ private int order = Ordered.HIGHEST_PRECEDENCE; @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) { ModelAndView model = new ModelAndView(new MappingJacksonJsonView()); try { if (e instanceof TypeMismatchException) { LOG.warn("TypeMismatchException occurred. " + e.getMessage()); return buildBizErrors((TypeMismatchException) e, model); } else if (e instanceof BindException) { LOG.warn("BindException occurred. " + e.getMessage()); return buildBizErrors((BindException) e, model); } else if (e instanceof HttpRequestMethodNotSupportedException) { LOG.warn("HttpRequestMethodNotSupportedException occurred. " + e.getMessage()); return buildError(model, GlobalResponseStatusMsg.REQUEST_HTTP_METHOD_ERROR); } else if (e instanceof MissingServletRequestParameterException) { LOG.warn("MissingServletRequestParameterException occurred. " + e.getMessage()); return buildError(model, GlobalResponseStatusMsg.PARAM_MISS_ERROR); } else { LOG.error("System error occurred. " + e.getMessage(), e); return buildError(model, GlobalResponseStatusMsg.SYSTEM_ERROR); } } catch (Exception ex) { // Omit all detailed error message including stack trace to external user LOG.error("Unexpected error occurred! This should never happen! " + ex.getMessage(), ex); model.addObject("status", SYS_ERROR_CODE); model.addObject("msg", SYS_ERROR_MSG); return model; } } } |
攔截器最多見的使用場景就是日誌、登錄、權限驗證等。下面以權限驗證爲例,通常狀況下,登錄的用戶會有不一樣的訪問權限,對於controller裏定義的方法進行有限制的調用,爲了更好的解耦,能夠定義一個公共的攔截器。
public class PriviledgeInterceptor implements HandlerInterceptor { @Override boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲取線程上下文的visitor Visitor visitor = ThreadContext.getSessionVisitor(); Preconditions.checkNotNull(visitor, "Visitor should NOT be null in ThreadContext!"); // 獲取權限集合 Set authSet = visitor.getAuths(); if (CollectionUtils.isEmpty(authSet)) { LOG.error("Visitor does NOT get any auths, userid=" + visitor.getUserid()); returnJsonSystemError(request, response, GlobalResponseStatusMsg.AUTH_DENIED); return false; } // 結合controller裏定義方法的註解來作驗證 HandlerMethod handlerMethod = (HandlerMethod) handler; Privilege privilege = handlerMethod.getMethodAnnotation(Privilege.class); if (privilege != null) { if (authSet.contains(privilege.value())) { return true; } LOG.error("Visitor does NOT have auth={} on controller={}, userid={}", new Object[] { privilege.value(), getBeanTypeAndMethodName(handlerMethod), visitor.getUserid() }); returnJsonSystemError(request, response, GlobalResponseStatusMsg.AUTH_DENIED); return false; } } } |
controller定義以下:
@Controller @RequestMapping("/test") @Privilege(PriviledgeConstant.BEIDOU_CPROUNIT) public class SiConfController { } |
對於不少的類似邏輯,能夠利用模板模式,把公共的操做封裝到父類controller中。例如對於一個下載報表的需求,能夠隱藏具體的寫流等底層操做,將這些模板抽象化到父類BaseController中,子類只須要去實現傳入一個調用獲取報表數據Callback來,這和Hibernate的callback思想殊途同歸。
@RequestMapping(value = "/downloadDtList") @ResponseBody public HttpEntity<byte[]> download(@RequestParam(value = PortalReportConstants.DOWNLOAD_POST_PARAM, required = true) String iframePostParams) { return toHttpEntity(new ReportCallback<ResultBundle<?>>() { public ResultBundle<GenderViewItem> call(QueryParamForm form) { return reportService.queryGenderReport(toQP(form)); } }); } |
上面的記錄是在2014年春作報表系統重構出web-ui模塊的一些最佳實踐,可做爲一個系統中的portal,可前端js或者API客戶端打交道的公共複用模塊。深刻到SpringMVC的源代碼才感覺的到其強大之處,但願你與我共勉,知其然還要知其因此然。