Spring MVC使用AOP攔截Controller
- 2020 年 4 月 2 日
- 筆記
接着上篇文章《SpringMVC快速使用AOP》繼續,如果我們需要對Controller進行切面編程,加上註解後,會發現我們的LogAspect竟然無法攔截到Controller層,仔細查找原因後,發現我們的代碼並無過錯。但是,我們確實有攔截Controller層的需要,比如日誌記載,比如權限控制等等。有的時候,我們不止需要攔截Controller,還需要獲取到HttpServletRequest,那麼該如何解決這個問題呢?
其實並不是什麼Spring的Controller層已經被AnnotationMethodHandlerAdapter給攔截了,真正的原因是:我在配置該Demo項目的時候採用了applicationContext.xml和spring-servlet.xml兩個配置文件,其中值得一提的是:spring-servlet.xml配置文件可以直接丟在web文件夾下,而不用在web.xml中配置,我親自試過有效。但是為了方便文件管理,還是和applicationContext.xml一起放在resource路徑下哦。
我們必須先明白這兩個配置文件在SpringMVC中的作用,applicationContext.xml會在ContextLoaderListenerclass被初始化時加載,Spring會創建一個WebApplicationContext上下文,稱為父上下文(父容器) ,保存在 ServletContext中,keyWebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值。而spring-servlet.xml可以配置多個,它代表每個 DispatcherServlet有一個自己的上下文對象(WebApplicationContext),稱為子上下文(子容器),子上下文可以訪問父上下文中的內容,但父上下文不能訪問子上下文中的內容。 它也保存在 ServletContext中,key是"org.springframework.web.servlet.FrameworkServlet.CONTEXT"+Servlet名稱。
那麼問題來了,當spring在加載父容器的時候就會去找切入點,但是這個時候切入的controller是在子容器中的,父容器是無法訪問子容器,所以就攔截不到。所以只需將上文配置的丟到spring-servlet.xml子配置文件中去即可。注意這裡了,這是SpringMVC web項目案例,如果是測Server服務,還是放在applicationContext.xml中哦。
這裡,對Aspect代碼進行更新,貼出最簡代碼:
@Aspect public class LogAspect { private final static Logger logger=Logger.getLogger(LogAspect.class); @Autowired private LogService logService; //Controller層切點 @Pointcut("@annotation(com.annotation.LogAnno)") public void controllerAspect() { } @Before("controllerAspect()") public void doBefore(JoinPoint joinPoint) { try { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpSession session = request.getSession(); //讀取session中的用戶 User user = (User) session.getAttribute("user"); LogEntity logEntity=new LogEntity(); logEntity.setCdate(new Date()); String ip=""; if (null==request.getHeader("x-forwarded-for")) { ip=request.getRemoteAddr(); }else{ ip=request.getHeader("x-forwarded-for"); } logEntity.setIp(ip); if(null==user){ logEntity.setUser_id(0); }else{ logEntity.setUser_id(user.getId()); } String operation=getControllerMethodDescription(joinPoint); logEntity.setOperation(operation); logService.insertLog(logEntity); logger.info("-----------訪問切面------------"+JsonUtil.parseObjectToJSON(logEntity)); } catch (Exception e) { logger.error("記錄日誌異常:{}",e); } } /** * @Title: getUserIp * @Description: 獲取用戶的訪問IP * @param @param request * @param @return 設定文件 * @return String 返回類型 * @throws */ protected String getUserIp(HttpServletRequest request){ logger.error("獲取IP信息"); if (null==request.getHeader("x-forwarded-for")) { return request.getRemoteAddr(); } else{ return request.getHeader("x-forwarded-for"); } } /** * 獲取註解中對方法的描述信息 用於Controller層註解 * * @param joinPoint 切點 * @return 方法描述 * @throws Exception */ public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception { String targetName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName); Method[] methods = targetClass.getMethods(); String description = ""; for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { description = method.getAnnotation(LogAnno.class).operation(); break; } } } return description; } }
代碼至此,AOP功能基本完成。只要我們在需要攔截的方法前加上我們自定義註解,即可攔截該方法,並將日誌計入數據庫。非常方便,也不用如何改動之前代碼。
建議大家多閱讀官網:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html,只不過這些細節問題,官網不會提及。代碼寫的很費勁,主要是這個註解必須每次重新發佈,累。不知道是本人eclipse原因,還是其他,在此記上一筆。歡迎各位大神指正。

