本文可作為北京尚學堂spring課程的學習筆記
首先談談什麼是AOP 它能幹什麼
AOP Aspect Oriented Programming(面向切面的程式設計)
什麼叫面向切面?
就是我們可以動态的在原來的程式邏輯前後(或其他特定的位置)加上我們自己想要的其他邏輯 這個原始的程式并不"知道" 我們在他前後做了補充
就例如我在前幾篇文章裡談動态代理時說的 我要記錄坦克運作所耗的時間 在它運動之前記錄時間點 運動後記錄時間點 然後記錄時間差 而這個記錄時間的動作與坦克本身的運動是沒有關系的
既然談到了動态代理 我建議如果沒有看過動态代理這個設計模式的朋友在學習aop之前最好還是先看看 否則會有聽不懂看不懂的危險(個人推薦 大家可以看看我前幾篇文章 從坦克聊代理模式)
AOP有如下的幾個概念
JoinPoint
PointCut
Aspect(切面)
Advice
Target
Weave
不過我現在并不準備和大家聊這個 等我們做出一個執行個體後 再說
我們還是做這樣一個例子 在使用者對資料庫做插入操作前後 加一點别的操作
先是完整的項目圖

最基本的spring
首先我們提煉出一個接口 往資料庫裡插入一個使用者
package com.bjsxt.dao;
import com.bjsxt.model.User;
public interface UserDao {
public void save(User u);
}
下面是UserDao的實作類UserDaoMysql 為了簡便 我們隻是列印出已經寫入mysql
package com.bjsxt.dao;
import org.springframework.stereotype.Component;
import com.bjsxt.model.User;
@Component
public class UserDaoMysql implements UserDao {
@Override
public void save(User u) {
// TODO Auto-generated method stub
System.out.println("已經寫入mysql");
}
}
相應的service 不解釋了
package com.bjsxt.services;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import com.bjsxt.dao.UserDao;
import com.bjsxt.dao.UserDaoMysql;
import com.bjsxt.model.User;
@Component
public class UserService {
@Resource
private UserDao userDaoMysql;
public UserDao getUserDao() {
return userDaoMysql;
}
public void setUserDao(UserDaoMysql userDao) {
this.userDaoMysql = userDao;
}
public void Save(User u){
userDaoMysql.save(u);
}
}
相應的配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
<context:annotation-config/>
<context:component-scan base-package="com.bjsxt"/>
<!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy> -->
</beans>
這是測試函數 最簡單的spring應用
package com.gc.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.bjsxt.model.User;
import com.bjsxt.services.UserService;
import com.gc.action.HelloWorld;
public class TestHelloWorld
{
public static void main(String args[])
{
ApplicationContext actx=new ClassPathXmlApplicationContext("Spring-Customer.xml");
User user=new User();
UserService u=(UserService)actx.getBean("userService");
u.Save(user);
}
}
至于User就不贅述了 User裡面一個username一個password 然後是getset方法
測試結果很簡單 就是列印出一句
已經寫入mysql
現在我們就給這個插入動作的前後加上日志記錄功能
package com.bjsxt.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogInterceptor {
@Pointcut("execution(public * com.bjsxt.services.UserService.Save(..))")
public void myMethod(){};
@Before("execution(public * com.bjsxt.services.UserService.Save(..))")
public void beforess() {
System.out.println("method before");
}
@After("execution(public * com.bjsxt.services.UserService.Save(..))")
public void afterss() {
System.out.println("method after");
}
// @Around("myMethod()")
// public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
// System.out.println("method around start");
// pjp.proceed();
// System.out.println("method around end");
// }
}
寫好這個類後 得引入四個jar檔案
如下圖
aopalliance-1.0.jar aspectjrt.jar aspectjweaver-1.6.12.jar cglib-nodep-2.1_3.jar
多說幾句 如出現error at ::0 can't find referenced pointcut這個問題
是因為 引入的aspectjweaver 版本太低
我自己用的是jdk1.7 eclipse3.7 spring3.2.0 aspectjweaver1.6.12
再一方面就是就是把xml裡面
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
前後的注釋去掉
///以下為2015年10月2日 修訂
最近在看spring的說明文檔的時候,看到下面的示例:
咦?befor中還可以直接寫方法,而不用加execution?
這個befor裡面加不加execution有什麼差別麼?
百度了半天,沒有找到差別,似乎都是加execution的
先不管了,試試不加execution的,結果報下面這個錯誤
error at ::0 can't find referenced pointcut
我更新了aspectjweaver,從1.8.6到1.7.3到1.7.4都是不行呀!!
在磨蹭了很近之後,我得出一個結論,before後面必須得加execution
後來,看了pointcut之後,我的結論又被推翻了,請看後面關于pointcut的說明
///以上為2015年10月2日 修訂
運作後結果
method before
已經寫入mysql
method after
好 大功告成 現在咱們慢慢聊聊aop的實作過程
aop的過程
第一關于cglib
如果看了動态代理的朋友 現在肯定要抛出一個問題
我們要代理的類 不是在jdk裡要實作InvocationHandler接口麼? 這裡怎麼沒有
(如果你沒有想到這個問題 說明你動态代理掌握的還不夠)
答案是spring用的是cglib這個工具來直接操縱二進制檔案 自然就不用實習接口了 看看上面那個所需要的jar包圖 是不是有一個cglib jar
第二 幾個術語
JoinPoint
@Before("execution(public * com.bjsxt.services.UserService.Save(..))")
public void beforess() {
System.out.println("method before");
}
上面的代碼表示 我要在com.bjsxt.services包下UserService類裡面的Save方法之前(參數不論 傳回值不論 另外這個之前是因為 @Befor 而不是函數名 是以我給函數名後面加了ss 以示差別)執行beforess這個方法
這下說的夠清楚了吧
com.bjsxt.services包下UserService類裡面的Save方法就是JoinPoint
/以下為2015年10月2日修訂
咱們再說說說execution裡面的表達式
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-pattern 特定的方法
param-pattern 參數
throws-pattern 異常的類型
關于這個,給幾個例子大家看看就明白了了
掌握這麼多就夠了,剩下的那種特别複雜的文法,大家不看也罷
另外
@Before("execution( public * com.bjsxt.service..*.add(..) ) || execution( public * com.bjsxt.service..*.delete(..) ) ")
public void before() {
System.out.println("my method before");
}
多個表達式也可以用||連接配接起來,既然有||那自然就用&&,有!喽
/以上為2015年10月2日修訂
PointCut
@Pointcut("execution(public * com.bjsxt.services.UserService.Save(..))")
public void myMethod(){};
這裡我還是用了上面的那個JoinPoint 其實execution括号裡面可以寫一個"方法簇"
這一系列JoinPoint 合起來就是一個PointCut 我們給他起了一個名字叫myMethod
其他的操作在指定插入點的時候在Advice裡面指定這個名字即可
@Around("myMethod()")
public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("method around start");
pjp.proceed();
System.out.println("method around end");
}
請注意,這個myMehod不是一個方法,而是一個pointcut的名字,這pointcut是com.bjsxt.services.UserService.Save的所有不同傳回值,不同參數的方法組成的集合
現在想想之前那個before,它後面是可以直接加一個字元串,并且不以execution開頭
問題是那個字元串并不是指定在什麼地方織入我們的邏輯,它隻是用來表面我引用的是那個pointcut而已
Aspect
我們這個LogInterceptor類就是一個切面 類的前面我們打上了标簽 @Aspect
Advice
就是執行附加邏輯的時間
@Before @After @AfterReturning @AfterThrowing @Around
最常用的也就是這幾個 前兩個我想不用解釋了
/一下為2015年10月2日修訂
@AfterThrowing這個是抛出異常後執行的,這個我們舉個例子
@AfterThrowing(pointcut="myMethod()",throwing="ex")
public void afterThrowdMethod(DataAccessException ex) {
System.out.println(ex.getMessage()+" ex message");
System.out.println("after throw");
}
上面的代碼說明,在myMethod指定的pointcut範圍内,如果抛出了DataAccessException類型的exception,就執行下面的方法(當然,如果抛出的是其他類型的,這裡就不管了)
AfterThrowing在什麼地方有應用?
struts2的異常處理。
以下為2015年10月2日修訂
@Around是可以同時在所攔截方法的前後執行一段邏輯
例如上面那個aroundMethod方法 單個運作它(為什麼是單個 大家可能會想pjp.proceed()前面的邏輯和 @Before裡面的邏輯哪個先執行呢? 這個沒有什麼意思 大家自己做個實驗就ok 總之一句話 不要把 前後的邏輯順序按照這個around和before來差別)
結果是
method around start
已經寫入mysql
method around end
當我把aroundMethod方法改成如下
@Around("myMethod()")
public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("method around start");
pjp.proceed();
System.out.println("method around end");
pjp.proceed();
System.out.println("method around endssss");
}
結果就成了
method around start
已經寫入mysql
method around end
已經寫入mysql
method around endssss
這個 @Around裡面pjp.proceed();就是指代原始邏輯的運作
Weave
weave是編織的意思 aop是這樣的運作的 "橫"着運作業務邏輯 "縱"着加入其它如日志權限等操作。
就像織布一樣橫着看是一種邏輯 縱着看又是一種邏輯。
Weave一般翻譯成織入 就表示這個過程。
使用xml配置aop
這個很簡單,大家一看就懂
package com.bjsxt.aop;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
@Component
public class LogInterceptorXML {
public void beforess() {
System.out.println("my method before");
}
public void afterThrowdMethod(DataAccessException ex) {
System.out.println("after throw");
}
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<context:component-scan base-package="com.bjsxt"/>
<aop:config>
<aop:pointcut
expression="execution( public * com.bjsxt.service..*.add(..) ) ||
execution( public * com.bjsxt.service..*.delete(..) ) "
id="myPointCut" />
<aop:aspect ref="logInterceptorXML">
<aop:before method="beforess" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
如果會用注解形式的aop,那麼xml式的真的就是看一眼就懂
。
這裡,我還得說,相比較于注解形式,我們更得掌握xml式的配置。