spring AOP切面編程
面向切面編程就是將程序中經常用到的功能抽取出來形成獨立於程序業務邏輯的一個切面,當你的程序要用到的時候不要修改原來的業務代碼就能將切面的功能嵌入到你的程序里面。而spring AOP 正是來幫我們實現這樣的功能的。通過spring AOP能降低程序耦合性,比如在程序中經常要記錄操作日志,安全認證等功能。傳統上來說我們會寫一個類用來專門寫日志,一個類需要寫日志的時候只需要調用那個寫日志的類執行相應的方法就行了,這樣一個類就對另一個類產生了依賴。通過spring AOP我們不用在我們的業務代碼里關心業務以外的代碼,只需要實現我們的業務代碼就行,其他的都給切面。這個有點像python中的裝飾器,對python熟悉的就容易理解了,而在python中也很容實現,這是函數式編程的一大特點吧,但是對於純面向對象的java來說實現就得借助動態代理來實現了,本博客只是簡單的介紹什么是spring AOP以及其用法,對於其實現原理感興趣的可以自己研究。
從一個小需求說起:一天老板對你說:你把這個程序中所有函數的執行時間給我打印出來,???沒接觸過spring AOP的一個一個方法的改啊改啊。。。。。生無可戀有沒有,所有的方法。。。。怎么辦??這么簡單的事,用spring AOP立馬就解決。
一個小的demo
intellij idea建一個maven項目
file -->new Project --->Maven
選擇自己的倉庫位置
設置項目位置,點下一步項目就創建好了。
配置程序的依賴包,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>spring_aop</groupId> <artifactId>spring_aop</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>spring_aop Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--spring相關包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.1.RELEASE</version> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.10</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.2</version> </dependency> </dependencies> <build> <finalName>spring_aop</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.0</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
main 下新建一個resource文件,並標志為Resource Root,並創建一個bean.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--目標類--> <bean id="stdServer" class="com.cqh.spring.demo.services.StudentServer"></bean> </beans>
main下新建java文件夾並將java文件夾標志為Source
新建pacakge和類
StudentServer類
package com.cqh.spring.demo.services; public class StudentServer { public void addStudent(){ try { //業務邏輯, System.out.println("執行新增學生業務"); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } public void updateStudent(){ try { //業務邏輯, System.out.println("執行修改學生業務"); Thread.sleep(50000); } catch (InterruptedException e) { e.printStackTrace(); } } }
main類
package com.cqh.spring.demo.main; import com.cqh.spring.demo.services.StudentServer; import org.springframework.context.support.ClassPathXmlApplicationContext; public class main { public static void main(String[] args){ ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); StudentServer s = (StudentServer)ac.getBean("stdServer"); s.addStudent(); } }
現在執行,會看到addStudent運行了,只是打印了執行新增學生業務,並沒有輸出方法的執行時間
執行新增學生業務
接下來神奇事就要發生了,在bean.xml中添加如下配置,同時TiemAspect的代碼如下
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--目標類--> <bean id="stdServer" class="com.cqh.spring.demo.services.StudentServer"></bean> <!--切面類--> <bean id="timeAspect" class="com.cqh.spring.demo.aop.TimeAspect"></bean> <!--切面配置--> <aop:config> <aop:pointcut id="std" expression="execution(* com.cqh.spring.demo.services.*.*(..))"/> <aop:aspect ref="timeAspect"> <aop:around method="recorderTime" pointcut-ref="std"/> </aop:aspect> </aop:config> </beans>
package com.cqh.spring.demo.aop; import org.aspectj.lang.ProceedingJoinPoint; import java.util.Date; /** * 切面類,獨立於業務邏輯外的功能 */ public class TimeAspect { /** * 記錄方法的執行時間 * @param point */ public void recorderTime(ProceedingJoinPoint point){ Long startTime = new Date().getTime(); try { point.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } Long endTime = new Date().getTime(); System.out.print("執行時間:"+(endTime-startTime)+"ms"); } }
再次運行main,等了大約10s,會發現時間已經出來了吧,在沒有修改addStudent方法業務代碼的基礎上,把addStudent方法的執行時間輸出來了吧,這樣任務就完成了。
總結:
spring AOP面向切面:在不修改原有業務功能的基礎給原來的方法擴展功能,其方式之一:使用配置,方式二:使用@Aspect相關注解。這里使用的是配置的方式。
要配置的東西如下:
1. 目標方法,也就是你要給他擴展功能的方法 ,也就常說的切入點表達式
這里對應於expression="execution(* com.cqh.spring.demo.services.*.*(..)) 的內容
切入點的完整的格式 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
modifiers-pattern 修飾符 可選 public private protected
ret-type-pattern 返回類型 必選 * 代表任意類型
declaring-type-pattern 方法的聲明類型
name-patterm 方法名稱類型
set* 以set開頭的所有的方法名稱
update* 以update開頭的所有的方法名稱
param-pattern 參數匹配
(..) 任意多個參數,每個參數任意多個類型
(*,String) 兩個參數 第一個是任意類型,第二個是String
(String,*,Integer) 三個參數,第一個是String類型,第二個是任意類型,第三個是Integer類型
throws-pattern 異常的匹配模式
例子:
execution(* com.cqh.spring.demo.services.*(..));cn.itcast.spring.aop.xml.AService下的所有的方法
execution(public * com.cqh.spring.demo.services.*.*(..)) 返回值為任意類型,修飾符為public,在com.cqh.spring.demo.services包及子包下的所有的類的所有的方法
exectuion(* com.cqh.spring..*.update*(*,String))返回值是任意類型,在com.cqh.spring包及子包下所有的以update開頭的參數為兩個,第一個為任意類型第二個為String類型的所有類的所有的方法
2.設置通知,也就是在切面類里面的方法
在spring AOP里面有5中類型的通知
前置通知就是在目標方法執行之前執行,使用<aop:before method="func1" pointcut-ref="std"/>來設置 ,這樣會找到其切面類里面對應的func1方法(根據method屬性),通過pointcut-ref找到切入點這里是
<aop:pointcut id="std" expression="execution(* com.cqh.spring.demo.services.*.*(..))"/>
也就是只要能匹配上這個切入點表達式的方法在執行之前都會執行func1,而我們可以在func1里面決定是否繼續執行目標方法
應用:一個用戶發來一個請求,會調用查看用戶信息的方法,需要權限的控制,而權限的控制不屬於業務,因此就需要在切面授權,有權限放行繼續執行查看用戶信息的業務方法
否則不再執行
public void Auth(ProceedingJoinPoint point){ boolean authentic = false; //查詢是否有權限,代碼省略 if (authentic){ //放行 try { point.proceed();//執行后面的通知或目標方法 } catch (Throwable throwable) { throwable.printStackTrace(); } }else{ System.out.println("no privilege !!!!!!!"); } }
后置通知在目標方法之后執行,無論目標方法是否發生異常都會執行,使用<aop:after method="" pointcut-ref="xxxx" />
返回后通知在目標方法成功執行后執行,使用<aop:after-returning method="func3" pointcut-ref="" returning="ret"/>
/** * * @param point * @param ret 目標方法的返回值 */ public void func3(JoinPoint point, Object ret){ }
環繞通知,在目標方法之前和目標方法執行之后都會執行。這里打印目標方法執行的時間就是用的環繞通知。
拋出異常后通知使用 <aop:after-throwing method="error" throwing="e" pointcut-ref="xxx"/>
/** * * @param point * @param e 目標方法拋出的異常 */ public void error(JoinPoint point,Throwable e){ }
到這基本的使用配置的方式就介紹完了。思考:怎么使用spring aop記錄詳細的日志,比如每個業務方法在調用時,記錄如下信息:當前登錄的用戶 操作的業務 調用的方法,操作的時間給記錄到數據庫