上兩週寫了兩篇Spring IOC相關的文章:Spring使用之IOC、Spring之IOC補充主要講Spring的核心功能IOC。Spring做爲一個輕量級容器,其核心功能就是IOC。但也不只僅是IOC,它提供的另外一個核心功能就是AOP(Aspect Oriented Programming)即面向切面編程。spring
面向對象編程已經給咱們提供了對現實業務很好的抽象,也實現數據的封裝,既然咱們已經有OOP,那麼咱們爲何還須要AOP呢??你想想這樣一個業務場景,假如咱們銀行有500個業務,假若有一天有這麼一個需求,要統計天天全部業務被操做的總數??你怎麼實現??每一個業務邏輯方法加上一個統計??那若是咱們有成千上萬的業務呢??你每一個業務方法都去修改,這樣的工做量會有多大,何況統計好像不是業務,不該該寫在業務方法裏面,還有你修改了全部業務方法,那是否是全部的業務都得從新測試,有了AOP這個問題就迎刃而解。express
Spring AOP就如同在選定邏輯方法上統一織入一段邏輯,咱們可織入統計的邏輯,也可作安全驗證。編程
一、 Joinpoint 鏈接點(《Spring 實戰》中的翻譯),可被織入操做的系統點即爲Joinpoint,便可被加入邏輯代碼的點,常見Joinpoint類型:安全
(1)、方法調用(方法調用處,調用對象上的執行點)。bash
(2)、方法調用執行(方法內,被調用方法邏輯執行時點,Spring僅支持該類JoinPoint)。app
(3)、構造方法調用。post
(4)、類初始化(靜態類型,靜態塊初始化時點)測試
二、PointCut(切點) 用來描述一組Joinpoint,指定系統中符合條件的一組Joinpoint。spring一般經過AspectJ的切點表達式來描述。ui
在Spring中全部的方法方法均可以做爲鏈接點,可是咱們不須要統計全部方法的調用次數,咱們可能只須要統計用戶業務的總數,這是咱們就得經過表達式來指定咱們須要統計的業務方法,經過表達式指定的的鏈接點就是切點,咱們要織入的統計代碼就應用在這些切點上。this
三、Advice(通知) :織入PointCut的邏輯內容 。
顯然咱們上面的業務需求中須要作的是統計,Advice根據織入內容在PointCut執行位置又可分爲一下幾種
(1)、Before Advice 指定PointCut方法執行以前執行。
(2)、After Advice 指定位置以後執行,After Advice包括後面三種,執行位置以下方法展現(After Advice、After Returning Advice 、After throwing Advice)。
執行位置分別對應以下:
public void mockMethod(){
//before Advice 織入邏輯執行點
try{
...........
retrun;
//after Returning Advice 織入邏輯執行點
}catch(Exception e){
//After throwing Advice 織入邏輯執行點
}finally{
//After Advice 織入邏輯執行點
}
}複製代碼
(3)AroundAdvice ,方法執行先後,這是比較特殊的通知,以前的兩種通知都不能阻止調用業務方法,但這個通知可阻止對業務方法的調用,好比,咱們可以使用AroudAdvice在邏輯方法前加一道安全驗證,驗證不經過則不讓調相應的邏輯方法。(這裏不理解沒關係,後面會給例子)。
接下來,咱們就上面說的銀行的例子來寫個demo,見識見識AOP。如今咱們須要統計業務辦理數,咱們可直接在業務方法執行前作一個統計,因此咱們通知使用Before Advice,Spring使用切面也又兩種方式:XML配置、註解。
使用AOP除了以前第一篇文章裏說的須要引進Spring核心包外,還須要引進新的兩個包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
複製代碼
項目結構以下
咱們經過一個靜態的int變量來統計全部業務的使用數,CountBusiness以下:
package cn.springstudy.inspectadvice;
import org.springframework.aop.BeforeAdvice;
public class CountBusiness{
public static int count;
public void beforeBusinessCount(){
count++;
}
}
複製代碼
咱們建全部銀行業務都放在cn.springstudy.bank下,咱們如今加上兩個銀行業務處理類:
package cn.springstudy.bank;
//帳號業務
public class AccountBusiness {
//更改用戶帳號綁定電話
public void chgAccountPhone(){
System.out.println("Change Account Phone");
}
//開通新帳號
public void createNewAccount(){
System.out.println("Create New Account");
}
//更改用戶信息
public void chgAccountInfo(){
System.out.println("Change Account Info Success");
}
//銷戶
public void delAccount(){
System.out.println("Delete Account");
}
}複製代碼
package cn.springstudy.bank;
//轉帳業務
public class TransferBusiness {
//轉帳
public void transfer(){
System.out.println("Transfer A Account To B Account");
}
}
複製代碼
接下來,須要在配置文件中配置
<?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">
<!-- 使用Spring AOP -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
<bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
<!--Advice類 -->
<bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
<!--AOP配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="countBusiness">
<!--切點,表示執行cn.springstudy.bank下的任意類的任意方法,expression中的表單
是即爲AspectJ表達式,指定須要被織入邏輯的切點,這裏表達式可先不深究,後面會講解 -->
<aop:pointcut id="bankbusinesspointcut"
expression="execution(* cn.springstudy.bank.*.*(..))">
</aop:pointcut>
<!--配置BeforeAdvice 執行織入邏輯爲bean
countBusiness的beforeBusinessCount方法,應用在bankbusinesspointcut切點上-->
<aop:before method="beforeBusinessCount"
pointcut-ref="bankbusinesspointcut" >
</aop:before>
</aop:aspect>
</aop:config>
</beans>複製代碼
接下來就能夠作測試了
package cn.springstudy.spring;
import cn.springstudy.bank.AccountBusiness;
import cn.springstudy.bank.TransferBusiness;
import cn.springstudy.inspectadvice.CountBusiness;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringStudy {
public static void main(String arg[]){
//方式一
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");;
AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
//辦理修改帳號信息業務
accountBusiness.chgAccountInfo();
//辦理修改綁定電話業務
accountBusiness.chgAccountPhone();
TransferBusiness transferBusiness = (TransferBusiness) classPathXmlApplicationContext.getBean("transferBusiness");
//辦理轉帳業務
transferBusiness.transfer();
System.out.println(CountBusiness.count);
}
}複製代碼
OK,執行結構如咱們想的同樣,統計出來的業務辦理量是3:
能夠看到整個AOP功能的使用仍是很清爽的,咱們只是增長了一個統計的類CountBusiness,還有增長了點配置,能夠看出Spring的理念之一:非侵入式。有一天你不想用Spring了,那些業務類、CountBusiness依舊是Java類,仍是可使用。
咱們只需建CountBusiness類作下修改
package cn.springstudy.inspectadvice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.BeforeAdvice;
//這是一個切面類
@Aspect
public class CountBusiness implements BeforeAdvice {
public static int count;
//定義切點
@Pointcut("execution(* cn.springstudy.bank.*.*(..))")
public void declarePointCut(){}
//before Advice 執行邏輯
@Before("declarePointCut()")
public void beforeBusinessCount(){
count++;
}
}
複製代碼
此時咱們的XML配置就不在須要AOP配置了,只需將CountBusiness配置成一個普通Bean便可,配置文件改成一下,便可執行測試方法,執行結果同上。
<?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:c="http://www.springframework.org/schema/c"
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/c http://www.springframework.org/schema/c/spring-c.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
<bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
<!--切面類-->
<bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
</beans>
複製代碼
Around Advice:前面咱們說過使用Around Advice能夠可阻止對業務方法的調用。接下來咱們就來體驗一下。咱們在操做用戶業務前面加個驗證。
咱們增長一個用戶類:
package cn.springstudy.vo;
public class User {
private String name;
private String passWord;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}複製代碼
再更改下帳號業務的類,辦理業務,順便把要辦理的用戶傳過來
package cn.springstudy.bank;
import cn.springstudy.vo.User;
//帳號業務
public class AccountBusiness {
//更改用戶帳號綁定電話
public void chgAccountPhone(User user){
System.out.println("Change Account Phone");
}
//開通新帳號
public void createNewAccount(User user){
System.out.println("Create New Account");
}
//更改用戶信息
public void chgAccountInfo(User user){
System.out.println("Change Account Info Success");
}
//銷戶
public void delAccount(User user){
System.out.println("Delete Account");
}
}複製代碼
接下來是Advice 類:
package cn.springstudy.inspectadvice;
import cn.springstudy.vo.User;
import org.aspectj.lang.ProceedingJoinPoint;
public class Authenticate {
public void aroundAdvice(ProceedingJoinPoint joinPoint){
//拿到調用切點方法的參數
Object[] arg = joinPoint.getArgs();
if (arg[0] instanceof User){
//若是用戶名是zhangsan,不讓調切點方法,直接return
if ("zhangsan".equals(((User)arg[0]).getName())){
System.out.println("zhangsan 異經常使用戶,不讓辦理業務");
return;
}
try {
joinPoint.proceed();//執行切點方法,即邏輯方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
}複製代碼
配置文件
<?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:c="http://www.springframework.org/schema/c"
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/c http://www.springframework.org/schema/c/spring-c.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
<bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
<bean id="authenticate" class="cn.springstudy.inspectadvice.Authenticate"></bean>
<aop:config>
<aop:aspect ref="authenticate">
<aop:around method="aroundAdvice" pointcut-ref="authenticatepointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
複製代碼
接下來就能夠測試了
public class SpringStudy {
public static void main(String arg[]){
AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
User user = new User();
user.setName("zhangsan");
user.setPassWord("123456");
//zhangsan 過來辦理更改用戶信息的業務
accountBusiness.chgAccountInfo(user);
}
}
複製代碼
執行結果,能夠看到chgAccountInfo方法裏東西並無執行。
若是是李四辦理業務呢,沒錯李四能夠正常調用業務。
public class SpringStudy {
public static void main(String arg[]){
AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
User user = new User();
user.setName("lisi");
user.setPassWord("123456");
//zhangsan 過來辦理更改用戶信息的業務
accountBusiness.chgAccountInfo(user);
}
}複製代碼
前面說到Apring AOP支持部分SpectJ切點語言來指定要應用切面的切點,那麼ASpectJ切點語言到底是怎樣的語法。
支持的部分指示符包括:execution、within、this、target、args
一、execution是最經常使用的了,用來指定切點方法,接下來就看看以前咱們的配置:
execution(* cn.springstudy.bank.*.*(..))複製代碼
(1)、其中第一個*表示返回類型能夠是任意類型,固然咱們也能夠直接指定,好比:
execution(int cn.springstudy.bank.*.*(..)) 表示匹配的返回類型爲int的方法。execution(cn.springstudy.vo.User cn.springstudy.bank.AccountBusiness.*(..)) 匹配的是返回類型爲User的方法。
(2)、第二個*號表示任意類,第三個*號表示任意方法,括號裏面兩個點表示參數能夠是任意個數、任意類型。
因此execution(* cn.springstudy.bank.*.*(..))這個表達式表示的就是cn.springstudy.bank包下任意類,類中任意方法,返回類型,和參數都不作限定,因此指定的切點就是AccountBusiness、TransferBusiness這兩個類中的全部方法。
二、within匹配因此持有指定註解類型內的方法
within(cn.springstudy.bank.*) 效果同 execution(* cn.springstudy.bank.*.*(..))
within(cn.springstudy.bank..*) 其中..表示包括子包。因此這個表示cn.springstudy.bank下,包括子包中的全部類下的方法。
三、args:使用「args(參數類型列表)」匹配當前執行的方法傳入的參數爲指定類型的執行方法,例:args(cn.springstudy.vo.User) 表示參數爲User的方法。
四、target,用於匹配類型,例:target(cn.springstudy.bank.VIPBusiness)實現了VIPBusiness接口的類都會被匹配上
以上的全部指示器還能夠經過 and 和 or這些邏輯運算符結合在一塊兒,例:表示匹配cn.springstudy.bank包下類的方法,且方法參數類型爲User
within(cn.springstudy.bank.*) and args(cn.springstudy.vo.User)複製代碼
五、 this(),這個我測試,沒看出跟target有什麼區別,暫時不清楚跟target的區別。
未完,待續...............