背景:項目使用Spring 3.1.0.RELEASE,從dao到Controller層全部是基於注解配置。我的需求是想在自定義的Spring攔截器中通過request獲取到該請求對應於Controller中的目標method方法對象。Controller和攔截器代碼如下:
AdminController
@Controller @RequestMapping("/admin") public class AdminController { /** * init:初始頁面. <br/> * * @author chenzhou * @param request 請求 * @param response 響應 * @return 登陸頁 * @since JDK 1.6 */ @RequestMapping("/init") public ModelAndView init(HttpServletRequest request, HttpServletResponse response){ Map<String, Object> model = new HashMap<String, Object>(); List<Role> roleList = this.adminService.getRoleList(); model.put("roleList", roleList); return new ModelAndView(this.getLoginPage(), model); } //…… }
LoginInterceptor
public class LoginInterceptor extends HandlerInterceptorAdapter { /** * This implementation always returns <code>true</code>. */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /** * This implementation is empty. */ public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * This implementation is empty. */ public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
servlet xml配置文件定義:
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="interceptors"> <list> <bean class="com.chenzhou.examples.erm.util.interceptor.LoginInterceptor"/> </list> </property> </bean>
我的需求是想在preHandle方法中通過request獲取該請求訪問的目標Controller中的方法對象。之前找了很久也沒有找到比較好的方案,就采取了最老土的通過比較requestURL和Controller類和方法上的RequestMappingURL來進行獲取,這樣也能勉強實現,但是這種方式我自己都覺得特別惡心。首先,這種方式需要使用反射來獲取Controller中的所有方法,然后遍歷method數組,逐個進行RequestMappingURL的比對,效率低下。其次,如果RequestMapping定義了類似於@RequestMapping("/{id}")這種動態參數url,則無法進行比較。
因為上面這種方式不好,我就一直想找一個更好的方案。不得已只能向人求助,第一個就想到了Iteye上對於Spring研究得很熟悉的jinnianshilongnian龍年兄,我相信經常上iteye的博友們對龍年兄應該都很熟悉。龍年兄給了我一個方案,就是通過把handler對象轉換為HandlerMethod類型,然后直接getMethod,代碼如下:
/** * This implementation always returns <code>true</code>. */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("*********************preHandle********************"); System.out.println(handler.getClass()); HandlerMethod handlerMethod = (HandlerMethod) handler; System.out.println(handlerMethod.getMethod()); return true; }
注:HandlerMethod類是Spring 3.1.0.RELEASE版本中才有的,之前我使用的Spring 3.0.6.RELEASE版本,里面是找不到這個類的
根據龍年兄提供的方法,測試之后報錯,報錯信息如下:
*********************preHandle******************** class com.chenzhou.examples.erm.web.AdminController 2012-10-21 16:28:25 org.apache.catalina.core.StandardWrapperValve invoke 嚴重: Servlet.service() for servlet erm threw exception java.lang.ClassCastException: com.chenzhou.examples.erm.web.AdminController cannot be cast to org.springframework.web.method.HandlerMethod at com.chenzhou.examples.erm.util.interceptor.LoginInterceptor.preHandle(LoginInterceptor.java:37) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:891) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:827) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778) at javax.servlet.http.HttpServlet.service(HttpServlet.java:617) at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) ……
根據錯誤提示可以看出是HandlerMethod handlerMethod = (HandlerMethod) handler;這一步報錯了,根據System.out.println(handler.getClass());打印的結果可以得知handler是該請求訪問的Controller類,無法轉換成HandlerMethod對象。這次還是龍年兄幫我找出了原因,解決方案是使用
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
替換
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
因為DefaultAnnotationHandlerMapping只能返回Controller對象,不會映射到Controller中的方法級別。替換之后servlet xml配置如下:
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <list> <bean class="com.chenzhou.examples.erm.util.interceptor.LoginInterceptor"/> </list> </property> </bean>
重啟tomcat測試之后發現再次報錯,報了另外一個錯誤,具體信息如下:
2012-10-21 16:39:39 org.apache.catalina.core.StandardWrapperValve invoke 嚴重: Servlet.service() for servlet erm threw exception javax.servlet.ServletException: No adapter for handler [public org.springframework.web.servlet.ModelAndView com.chenzhou.examples.erm.web.AdminController.init(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]: Does your handler implement a supported interface like Controller? ……
這一次,請求根本沒有到達攔截器容器就已經報錯了,錯誤提示的意思是找不到handler對象對應的Adapter類。我在RequestMappingHandlerMapping類對應的spring-webmvc-3.1.0.RELEASE.jar 包里找到了該類對應的Adapter類:RequestMappingHandlerAdapter,然后在servlet xml中進行了配置:
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <list> <bean class="com.chenzhou.examples.erm.util.interceptor.LoginInterceptor"/> </list> </property> </bean> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
然后重新啟動tomcat后訪問http://localhost:8080/erm/admin/init 結果正常,控制台日志信息如下:
Shell代碼
*********************preHandle******************** class org.springframework.web.method.HandlerMethod public org.springframework.web.servlet.ModelAndView com.chenzhou.examples.erm.web.AdminController.init(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
從日志信息可以看出,handler對象在經過類型轉換后轉換成了HandlerMethod類型,通過handler.getMethod方法,獲取到了該請求訪問的方法為com.chenzhou.examples.erm.web.AdminController.init
注:非常感謝jinnianshilongnian 開濤兄的幫助。