Spring+Spring MVC+Mybatis整合配置AOP不生效的解決方案以及Bean初始化重複載入兩次(疑難雜症)
之前上班做spring+spring mvc +hibernate開發, 2年之久不做想複習一下aop的使用,結果配置遇見aop不生效,解決而記錄!
先上程式碼直接看反例效果會明顯:
首先看一下我的程式碼的包路徑:
接下來看Spring-MVC的配置檔案部分程式碼:
在看一下Spring的配置檔案:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd"> <context:component-scan base-package="cn.edu.neu" /> ...............//省略其他配置 </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd"> <!-- 自動掃描 <context:component-scan base-package="cn.edu.neu" /> <!-- aop 日誌管理 --> <aop:aspectj-autoproxy expose-proxy="true" /> <aop:config proxy-target-class="true"> <aop:aspect id="aspectId" ref="aopLog"> <!--呼叫日誌類 --> <!-- cn.edu.neu.service.impl.UserServiceImpl --> <aop:pointcut id="log" expression="execution(* cn.edu.neu.service.*.*(..))" /><!--配置在log包下所有的類在呼叫之前都會被攔截 --> <aop:before method="before" pointcut-ref="log"/><!--在log包下面所有的類的所有方法被呼叫之前都呼叫MyLog中的before方法 --> <aop:after method="after" pointcut-ref="log"/> <!--在log包下面所有的類的所有方法被呼叫之前都呼叫MyLog中的after方法 --> </aop:aspect> </aop:config> </beans>
web.xml配置檔案如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>Archetype Created Web Application</display-name> <!-- 配置資源的載入順序:context-param -> listener -> filter -> servlet --> <!-- Spring和mybatis的配置檔案 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mybatis.xml</param-value> </context-param> <!-- Spring監聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 編碼過濾器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <async-supported>true</async-supported> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Spring MVC servlet --> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <!-- 此處可以可以配置成*.do,對應struts的字尾習慣 --> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>/index.jsp</welcome-file> </welcome-file-list> </web-app>
以上的配置之後以為大功告成,結果一執行,恩?不好使?,接下來分析一下原因:
我們知道web.xml中載入順序是:context-param -> listener -> filter -> servlet ,所以在載入完引數之後開始啟動spring的監聽器,監聽器載入完成之後,就完成了bean載入到容器的過程。但是你可能到現在還沒有注意的是,在上面springmvc和spring的配置檔案都配置了<context:component-scan base-package="cn.edu.neu" />,所以導致在Spring的DispatcherServlet啟動過程中又一次把springmvc.xml掃描到的元件(就是類)再一次新增到spring的容器中,此次會覆蓋前面載入的,所以這個就是為什麼spring aop不生效的原因!!!
到此是不是還是有點不明白呢?那就在具體說一下:因為在第一次spring載入元件的時候會給pointcut物件生成代理(aop實現原始是動態代理),放到容器中。但是第二次載入時候生成的物件沒有代理,第二次覆蓋了第一次存在代理的物件,因此導致aop不生效!
接下來說一下解決方案:
方案1:將spring和spring mvc的配置檔案放到一個配置檔案中,即可避免兩次載入覆蓋。
方案2:如果非要兩個檔案都配置掃描的程式碼的話,那就把需要生成代理的包路徑在spring中配置掃描,確保spring mvc的掃描路徑不包括“需要生成代理的路徑”即可。(而spring mvc的controller元件掃描必要要在spring mvc中配置)
例如方案2程式碼如下:
spring的配置檔案:
掃描非controller路徑
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">
<!-- 自動掃描 -->
<context:component-scan base-package="cn.edu.neu.dao,cn.edu.neu.service,cn.edu.neu.log" />
<!-- aop 日誌管理 -->
<aop:aspectj-autoproxy expose-proxy="true" />
<aop:config proxy-target-class="true">
<aop:aspect id="aspectId" ref="aopLog"> <!--呼叫日誌類 -->
<!-- cn.edu.neu.service.impl.UserServiceImpl -->
<aop:pointcut id="log"
expression="execution(* cn.edu.neu.service.*.*(..))" /><!--配置在log包下所有的類在呼叫之前都會被攔截 -->
<aop:before method="before" pointcut-ref="log"/><!--在log包下面所有的類的所有方法被呼叫之前都呼叫MyLog中的before方法 -->
<aop:after method="after" pointcut-ref="log"/>
<!--在log包下面所有的類的所有方法被呼叫之前都呼叫MyLog中的after方法 -->
</aop:aspect>
</aop:config>
</beans>
spring-mvc配置檔案:
spring mvc配置檔案只負責掃描contrller的元件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">
<!-- 自動掃描該包,使SpringMVC認為包下用了@controller註解的類是控制器 -->
<context:component-scan base-package="cn.edu.neu.controller" />
</beans>
核心結論:就是需要生成代理的類一定不要被重複覆蓋載入(Spring bean的載入支援覆蓋),否則就會aop不起作用!
補充:
你通過配置方案1或者方案2之後,你在eclipse中啟動tomcat可能會發現還是載入兩次!!!這個問題坑了我一上午,記錄一下自己的心得吧!
得到下面結論的執行環境是:
1:Tomcat部署目錄預設的webapps,而且webapps中部署了一個該驗證的專案。
2:eclipse版本:Version: Mars.1 Release (4.5.1),tomcat版本:apache-tomcat-7.0.53-windows-x64
結論先行:
1:從新解壓一個全新的tomcat,然後將專案放到webapps中啟動發現只會載入一次,這是就可以確定原因了就是原來tomcat或eclipse的問題!接下來看如下分析:
2:只要你的tomcat配置過eclipse的話,當你使用eclipse啟動tomcat之後,會在tomcat的server.xml中生成如下程式碼:
<Context docBase="C:\install\tmp\apache-tomcat-7.0.53-windows-x641\apache-tomcat-7.0.53\webapps\springaop" path="/neuweb" reloadable="true" source="org.eclipse.jst.jee.server:springaop"/>
從此之後無論你是使用eclipse啟動tomcat還是bin/startup.bat啟動tomcat都會載入兩次bean!(Context docBase只要給專案起別名了的話就會載入兩次,但是如果當path的名字和docBase的最後的名字一樣的話就不會觸發載入兩次了,一樣的話其實就是起了一個和原來名字一樣的別名與沒起別名是一樣的。)
即如下不如載入兩次:
<Context docBase="C:\install\tmp\apache-tomcat-7.0.53-windows-x641\apache-tomcat-7.0.53\webapps\springaop" path="/springaop" reloadable="true" source="org.eclipse.jst.jee.server:springaop"/>
所以到此就可以確定原因了:就是因為專案有別名訪問導致最終實現兩次載入!
解決辦法:刪除Context程式碼之後,使用tomcat指令碼啟動tomcat,這時候bean就會載入一次!(如果使用eclipse啟動tomcat的話還是會生成context,還是載入兩次)
context docbase作用:就是你給的專案起一個訪問的別名,例如:你有一個叫appweb的專案,這時候就可以使用localhost:8080/appweb訪問該專案了,但是如果你還想使用localhost:8080/website路徑訪問appweb的話,就需要使用context docbase配置。具體配置不細說了,自行找資料學習!