11.SSM框架集~SpringAOP
本文是上一篇文章的後續,詳情點擊該鏈接
SpringAOP的介紹
在前面的文章裏,我們學習了SpringIOC之後,我們可以使用IOC的知識將代碼中層與層之間的耦合性進行解耦,便於後期維護。但是在實際生產環境中,我們發現隨着公司業務的增長,我們會升級某個業務層的業務方法的代碼邏輯。升級後的業務方法還需要兼容以前的邏輯處理,也就說再保留原有功能邏輯的基礎上,在方法中新增新的邏輯代碼。而這個時候,就需要我們去修改當前功能方法的源碼,增加新的邏輯代碼,然後重新運行項目.這時候出現如下問題:
1.假如我們當前項目已經有其他程序員寫好的代碼,就需要閱讀當前方法的源碼,然後再增加自己新的邏輯代碼.並且修改好後,需要將新的類文件替換舊的類文件,而閱讀代碼本身效率極低.
2.假如我們沒有源碼文件,那麼就無法直接修改源碼增加新的功能邏輯,怎麼辦?
解決:
在不修改原有功能邏輯的基礎上完成功能擴展.
方案:
SpringAOP
簡介:
AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
AOP, 在縱向執行過程中, 橫向的切一刀, 在原有代碼邏輯基礎上額外補充一些輔助功能. 動態代理的應用過程.
我們將要進行功能擴展相關的材料以及對應的組織規則告訴Spring容器,Spring容器幫我們動態創建一個代理對象。我們直接從Spring容器中獲取代理對象完成功能開發。
還是老規矩,先導包....
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alvin</groupId>
<artifactId>CodeSystem</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
</dependencies>
</project>
我們先來回顧一下傳統的方法
applicationContext.xml
User
Test
運行
我們看到正常方法在調用執行的時候,都是縱向執行,由上而下。
我們可以把這三個方法想象成是一年前寫的代碼,然後現在我準備對這個項目進行擴展。而我們AOP則是通過增加切面擴展
SpringAOP的專業概念
連接點:執行的所有方法都可以看成連接點
切點:要進行功能擴展的方法
前置通知:在切點之前執行的方法
後置通知:在切點之後執行的擴展方法
切面:由前置通知+切點+後置通知形成的橫向執行的面
織入:由前置通知+切點+後置通知形成切面的過程
AOP前置通知實現
需求:對方法Eat進行代碼功能擴展,在方法Eat之前,添加日誌記錄實現
aop:
切點: 方法Eat
通知: 前置通知
applicationContext.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="user" class="com.alvin.pojo.User"> </bean>
<!-- 創建通知類的對象 -->
<bean id="before" class="com.alvin.advice.BeforAdvice"/>
<!-- 進行切面的織入 -->
<aop:config>
<!-- 配置切點 -->
<aop:pointcut id="pt" expression="execution(* com.alvin.pojo.User.eat())"/>
<!-- 給指定的切點配置通知 -->
<aop:advisor advice-ref="before" pointcut-ref="pt"></aop:advisor>
</aop:config>
</beans>
BeforAdvice
//前置通知
public class BeforAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("擴展日誌操作--前置通知");
}
}
test
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = applicationContext.getBean("user",User.class);
user.run();
user.eat();
user.sports();
}
}
運行結果
此時我們會發現一個有趣的現象,明明Test類並沒有發生改變,可是執行的時候,卻多執行了一個方法。這就是我們的前置通知
那麼現在我們來添加一個後置通知吧
AOP後置通知實現
新增一個AfterAdvice類
public class AfterAdvice implements AfterReturningAdvice {
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("方法擴展代碼----後置通知");
}
}
applicationContext.xml配置
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="user" class="com.alvin.pojo.User"> </bean>
<!-- 創建通知類的對象 -->
<bean id="before" class="com.alvin.advice.BeforAdvice"/>
<bean id="after" class="com.alvin.advice.AfterAdvice"/>
<!-- 進行切面的織入 -->
<aop:config>
<!-- 配置切點 -->
<aop:pointcut id="pt" expression="execution(* com.alvin.pojo.User.eat())"/>
<!-- 給指定的切點配置通知 -->
<aop:advisor advice-ref="before" pointcut-ref="pt"></aop:advisor>
<!-- 給指定的切點配置後置通知 -->
<aop:advisor advice-ref="after" pointcut-ref="pt"/>
</aop:config>
</beans>
Test保持不變,運行看看
執行的順序正好如圖所示
AOP環繞通知實現
前面我們已經使用前置通知方式和後置通知方式完成了AOP的擴展代碼的編寫。而我們之前學過過濾器的概念,在過濾器中會先執行一部分代碼,執行後如果放行了則繼續執行Servlet,Servlet執行後再次回到過濾器中執行。那麼,從AOP的角度過濾器就相當於Servlet的擴展對象了。過濾器中的攔截方法,就相當於擴展方法,而我們將擴展代碼和調用原有切點方法的代碼全部直接聲明在一個方法中了,那麼能不能採用此種方案來完成我們會自己的AOP擴展呢?
新增一個AroundAdvice類
public class AroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("擴展方法前--環繞");
//執行切點中的方法
Object proceed = methodInvocation.proceed();
System.out.println("擴展方法後--環繞");
return proceed;
}
}
配置pom.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="user" class="com.alvin.pojo.User"> </bean>
<!-- 創建通知類的對象 -->
<bean id="before" class="com.alvin.advice.BeforAdvice"/>
<bean id="after" class="com.alvin.advice.AfterAdvice"/>
<bean id="around" class="com.alvin.advice.AroundAdvice"/>
<!-- 進行切面的織入 -->
<aop:config>
<!-- 配置切點 -->
<aop:pointcut id="pt" expression="execution(* com.alvin.pojo.User.eat())"/>
<!-- 給指定的切點配置通知 -->
<aop:advisor advice-ref="before" pointcut-ref="pt"></aop:advisor>
<!-- 給指定的切點配置後置通知 -->
<aop:advisor advice-ref="after" pointcut-ref="pt"/>
<!-- 給指定的切點配置環繞通知 -->
<aop:advisor advice-ref="around" pointcut-ref="pt"/>
</aop:config>
</beans>
Test保持不變,我們運行看看
AOP異常通知
在我們封裝一個功能方法時,一般方法處理數據所造成的異常信息需要拋出,或者 代碼編譯沒有問題,運行期間出現問題,該異常也應該有調用者來處理。那麼在Spring AOP中,代理對象是動態生成的,在代理對象中會調用前置通知,後置通知,環繞通 知,切點方法,那麼如果這些方法出現異常信息,理論上來說應該在擴展對象中的擴展 方法中完成異常的處理。但是尷尬的是,代理對象是動態生成的,不是由我們創建類然 後根據類文件創建出來的,那麼我們就無法直接的聲明異常處理代碼了,怎麼辦呢?
在外部聲明異常處理的功能方法,讓SpringAOP動態生成的代理對象,在生成的catch中調用我們聲明的異常處理方法即可。
新增一個ThrowsAdviceImpl類
public class ThrowsAdviceImpl implements ThrowsAdvice {
public void afterThrowing(Exception ex) throws Throwable{
System.out.println("異常通知操作");
}
}
applicationContext.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="user" class="com.alvin.pojo.User"> </bean>
<!-- 創建通知類的對象 -->
<bean id="before" class="com.alvin.advice.BeforAdvice"/>
<bean id="after" class="com.alvin.advice.AfterAdvice"/>
<bean id="around" class="com.alvin.advice.AroundAdvice"/>
<bean id="throws" class="com.alvin.advice.ThrowsAdviceImpl"/>
<!-- 進行切面的織入 -->
<aop:config>
<!-- 配置切點 -->
<aop:pointcut id="pt" expression="execution(* com.alvin.pojo.User.eat())"/>
<!-- 給指定的切點配置通知 -->
<aop:advisor advice-ref="before" pointcut-ref="pt"></aop:advisor>
<!-- 給指定的切點配置後置通知 -->
<aop:advisor advice-ref="after" pointcut-ref="pt"/>
<!-- 給指定的切點配置環繞通知 -->
<aop:advisor advice-ref="around" pointcut-ref="pt"/>
<!-- 給指定的切點配置異常通知 -->
<aop:advisor advice-ref="throws" pointcut-ref="pt"/>
</aop:config>
</beans>
隨便報個錯看看
public void eat(){
int a = 5 / 0;
System.out.println("喫飯");
}