target
掌握AOP概念和相關(guān)術(shù)語
會(huì)編寫基于XML的AOP編程和基于注解的AOP編程
理解MethodBeforeAdvice接口和execution表達(dá)式的使用
理解AOP底層原理
了解AOP中的一個(gè)"坑"
1. AOP概述
AOP (Aspect Oriented Programing) ?向切?編程 ,aop編程本質(zhì)上就是Spring動(dòng)態(tài)代理開發(fā)
以切?為基本單位的程序開發(fā),通過切?間的彼此協(xié)同,相互調(diào)?,完成程序的構(gòu)建
切? = 切?點(diǎn) + 額外功能
OOP (Object Oritened Programing) ?向?qū)ο缶幊?,比如:Java
以對(duì)象為基本單位的程序開發(fā),通過對(duì)象間的彼此協(xié)同,相互調(diào)?,完成程序的構(gòu)建
POP (Producer Oriented Programing) ?向過程(?法、函數(shù))編程 ,比如:C語言
以過程為基本單位的程序開發(fā),通過過程間的彼此協(xié)同,相互調(diào)?,完成程序的構(gòu)建
舉個(gè)例子來說明一下:
如果有一個(gè)UserService類,里面有這樣三個(gè)方法:addUser()、updateUser()、deleteUser()?,F(xiàn)在希望給這三個(gè)方法都加上事務(wù),但是不能修改這個(gè)類的源碼。所以我們想到寫一個(gè)子類去繼承他,然后修改子類就好了。
SonUserService類繼承UserService類,子類里可以直接調(diào)用父類的方法。比如:addUser()方法我就可以先調(diào)用父類的addUser()方法:super.addUser()。然后在前面開啟事務(wù),在后面提交事務(wù)。
我們發(fā)現(xiàn),后面兩個(gè)方法依次都要這么做,而且開啟事務(wù)、提交事務(wù)不斷重復(fù)出現(xiàn)。
這是我們以前寫的東西,我們稱之為縱向繼承。這就是傳統(tǒng)的oop思想。

現(xiàn)在有一個(gè)新的解決方案:
既然開啟事務(wù)和提交事務(wù)是重復(fù)代碼,可以把它抽取出來放在一個(gè)類里面。
寫一個(gè)A類,before()方法里面寫開啟事務(wù),after()方法里面寫提交事務(wù)。我希望A類的方法都要作用到UserService類的相應(yīng)方法的前后去,此時(shí)就可以用到代理。寫一個(gè)代理類,把這兩個(gè)無關(guān)的類建立起關(guān)系,就像房產(chǎn)中介就是代理類,他把買房子的和買房子的建立起關(guān)系。代理類把左邊的代碼拿過來,把右邊的代碼也拿過來,然后組合在一起。所以代理類這樣寫:
A.before();
UserService.addUser();
A.after();

有人可能說,你這么寫完和剛才有啥區(qū)別?不是一樣了嗎?
不一樣的。Spring提供了動(dòng)態(tài)代理,代理類其實(shí)是Spring去做的,我們只需要將A類寫好,UserService類寫好,然后都交給Spring去做了,這樣我們做的事就僅僅是業(yè)務(wù)邏輯了,該有的功能都有,但是代碼量卻少了。這就說我們即將學(xué)習(xí)的AOP。
AOP有如下特點(diǎn)和應(yīng)用:
經(jīng)典應(yīng)用:性能檢測(cè)、事務(wù)管理、安全檢查、緩存。
Spring AOP使用純Java實(shí)現(xiàn),不需要專門的編譯過程和類加載器,在運(yùn)行期通過代理方式向目標(biāo)類織入增強(qiáng)代碼。
1.1 AOP定義
AOP的概念: 本質(zhì)就是Spring的動(dòng)態(tài)代理開發(fā),通過代理類為原始類增加額外功能。 好處: 利于原始類的維護(hù),減少代碼量,使開發(fā)人員專注于業(yè)務(wù)邏輯的開發(fā)。 注意: AOP編程不可能取代OOP,OOP編程有益補(bǔ)充。
1.2 AOP術(shù)語

target:目標(biāo)類,需要被代理的類。也就是上圖的UserService類。
JointPoint:連接點(diǎn),指可能被攔截到的方法。例如:所有的方法。
-
PointCut:切入點(diǎn),已經(jīng)被增強(qiáng)的方法。例如:addUser()
怎么記連接點(diǎn)和切入點(diǎn):
比如:洗手間所有的馬桶就是連接點(diǎn),正在被使用的馬桶就是切入點(diǎn)
advice:通知/增強(qiáng),增強(qiáng)的代碼。例如:before()、after()
Weaving:織入,把增強(qiáng)advice應(yīng)用到目標(biāo)對(duì)象target來創(chuàng)建新的代理對(duì)象Proxy的過程。
Proxy:代理類
aspect:切面,切入點(diǎn)和通知的結(jié)合。
1.3 4種advice
如果要對(duì)一個(gè)切入點(diǎn)進(jìn)行增強(qiáng),首先考慮的應(yīng)該是在切入點(diǎn)的什么位置進(jìn)行增強(qiáng),是在切入點(diǎn)前面增強(qiáng),還是后面,還是前后一起?
| 通知類型 | 需要實(shí)現(xiàn)的接口 | 接口中的方法 | 執(zhí)行時(shí)機(jī) |
|---|---|---|---|
| 前置通知 | org.springframework.aop.MethodBeforeAdvice | before() | 目標(biāo)方法之前 |
| 后置通知 | org.springframework.aop.AfterReturningAdvice | afterReturning() | 目標(biāo)方法執(zhí)行后 |
| 異常通知 | org.springframework.aop.ThrowsAdvice | 無 | 目標(biāo)方法發(fā)生異常時(shí) |
| 環(huán)繞通知 | org.aopalliance.intercept.MethodInterceptor | invoke() | 調(diào)用目標(biāo)方法的整個(gè)過程 |
2. 基于xml的AOP編程
新建Java項(xiàng)目:Spring-06
2.1 前置通知
前置通知需要實(shí)現(xiàn)MethodBeforeAdvice接口。
① 代碼實(shí)現(xiàn)
第一步:導(dǎo)入jar
aopalliance.jar
aspectjweaver.jar
commons-logging-1.2.jar
spring-aop-4.3.9.RELEASE.jar
spring-beans-4.3.9.RELEASE.jar
spring-context-4.3.9.RELEASE.jar
spring-core-4.3.9.RELEASE.jar
spring-expression-4.3.9.RELEASE.jar
注意:必須有 aopalliance 和 aspectjweaver 的支持,否則 aop功能無法實(shí)現(xiàn)。
第二步:編寫目標(biāo)類
- UserService.java
package com.lee.spring.service;
public interface UserService {
void addUser();
void updateUser();
boolean deleteUser(int no);
}
- UserServiceImpl.java
package com.lee.spring.service.impl;
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("addUser...");
}
@Override
public void updateUser() {
System.out.println("updateUser...");
}
@Override
public boolean deleteUser(int no) {
System.out.println("deleteUser...");
return true;
}
}
第三步:編寫通知類BeforeAdvice.java
package com.lee.spring.aop;
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知執(zhí)行了。。。。");
}
}
第四步:編寫配置文件
<?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-4.3.xsd">
<!-- 將目標(biāo)類納入到IOC容器 -->
<bean id="userService" class="com.lee.spring.service.impl.UserServiceImpl"></bean>
<!-- 將通知類納入到IOC容器 -->
<bean id="beforeAdvice" class="com.lee.spring.aop.BeforeAdvice"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切入點(diǎn) -->
<aop:pointcut expression="execution(void com.lee.spring.service.impl.UserServiceImpl.addUser())" id="pointcut"/>
<!-- 將切入點(diǎn)和通知連接起來 -->
<!-- advisor就是顧問的意思,顧問就是給兩個(gè)點(diǎn)起一個(gè)橋梁作用 -->
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
第五步:測(cè)試
@Test
public void test01() {
//1.加載配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 獲取bean
UserService userService = (UserService)context.getBean("userService");
userService.addUser();
userService.deleteUser(1);
userService.updateUser();
}
輸出:
前置通知執(zhí)行了。。。。
addUser...
deleteUser...
updateUser...
可以通過 debug的方式來查看 addUser()方法、deleteUser()、updateUser()方法:

Debug As 進(jìn)行啟動(dòng):

可以發(fā)現(xiàn),userService對(duì)象使用的就是代理對(duì)象。
擴(kuò)展:
如果我們想給delete方法也配置一個(gè)前置通知,直接修改配置文件就可以:
<!-- 配置aop -->
<aop:config>
<!-- 配置切入點(diǎn) -->
<aop:pointcut expression="execution(void com.lee.spring.service.UserServiceImpl.impl.addUser()) or execution( boolean com.lee.spring.service.UserServiceImpl.impl.deleteUser(int))" id="pointcut"/>
<!-- 將切入點(diǎn)和通知連接起來 -->
<!-- advisor就是顧問的意思,顧問就是給兩個(gè)點(diǎn)起一個(gè)橋梁作用 -->
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointcut"/>
</aop:config>
② MethodBeforeAdvice詳解
MethodBeforeAdvice接?作?:額外功能運(yùn)?在原始?法執(zhí)?之前,進(jìn)?額外功能操作。
關(guān)于前置通知類BeforeAdvice中before()方法中的參數(shù)解釋
public class BeforeAdvice implements MethodBeforeAdvice {
/**
Method: 額外功能所增加給的那個(gè)原始?法
比如:login?法、register?法、showOrder?法
Object[]: 額外功能所增加給的那個(gè)原始?法的參數(shù)。
比如:String name,String password、User
Object: 額外功能所增加給的那個(gè)原始對(duì)象
比如:UserServiceImpl、OrderServiceImpl
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("method名字:" + method.getName());
System.out.println("method參數(shù)個(gè)數(shù):" + method.getParameterCount());
System.out.println( "args:" + Arrays.toString(args));
System.out.println("target:" + target);
System.out.println("前置通知執(zhí)行了。。。。");
}
}
輸出:
method名字:deleteUser
method參數(shù)個(gè)數(shù):1
method的返回值類型:boolean
args:[1]
target:com.lee.service.UserServiceImpl@6ab7a896
前置通知執(zhí)行了。。。。
Method: 切入點(diǎn)(目標(biāo)方法)
Object[] args:原始方法的參數(shù)
Object target:原始對(duì)象或目標(biāo)對(duì)象(UserServiceImpl對(duì)象)
③ execution詳解
例: execution (* com.sample.service..*.*(..))
整個(gè)表達(dá)式可以分為五個(gè)部分:
1、execution():表達(dá)式主體。
2、第一個(gè)*號(hào):表示返回類型, *號(hào)表示所有的類型。
3、包名:表示需要攔截的包名,后面的兩個(gè)句點(diǎn)表示當(dāng)前包和當(dāng)前包的所有子包,com.sample.service包、子孫包下所有類的方法。
4、第二個(gè)*號(hào):表示類名,*號(hào)表示所有的類。
5、*(..):最后這個(gè)星號(hào)表示方法名,*號(hào)表示所有的方法,后面括弧里面表示方法的參數(shù),兩個(gè)句點(diǎn)表示任何參數(shù)
??:
-
boolean addStudent(com.lee.entity.Student)返回值類型為boolean,參數(shù)類型為com.lee.entity.Student的所有叫addStudent()方法。
-
boolean com.lee.service.StudentService.addStudent(com.lee.entity.Student)返回值為boolean,參數(shù)類型為com.lee.entity.Student,在com.lee.service.StudentService類下的addStudent方法。
-
* addStudent(com.lee.entity.Student)返回值任意,參數(shù)類型為com.lee.entity.Student的所有叫addStudent()方法。
-
void *(com.lee.entity.Student)返回值為void,參數(shù)類型為com.lee.entity.Student的任意方法。
-
* com.lee.service.*.*(..)返回值任意,com.lee.service包下的所有類下的所有方法,參數(shù)類型任意。(不包含子包)
-
* com.lee.service..*.*(..)返回值任意,com.lee.service包下的所有類下的所有方法,參數(shù)類型任意。(包含子包)
2.2 后置通知
后置通知需要實(shí)現(xiàn)AfterReturningAdvice接口。
第一步:導(dǎo)入jar
aopalliance.jar
aspectjweaver.jar
第二步:編寫目標(biāo)類
- UserService.java
public interface UserService {
void addUser();
void updateUser();
boolean deleteUser(int no);
}
- UserServiceImpl.java
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("addUser...");
}
@Override
public void updateUser() {
System.out.println("updateUser...");
}
@Override
public boolean deleteUser(int no) {
System.out.println("deleteUser...");
return true;
}
}
第三步:編寫通知類AfterAdvice.java
public class AfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("返回值:" + returnValue);
System.out.println("method方法名:" + method.getName());
System.out.println("method參數(shù)個(gè)數(shù):" + method.getParameterCount());
System.out.println("method返回值類型:" + method.getReturnType());
System.out.println( "目標(biāo)對(duì)象:" +target);
System.out.println("后置通知執(zhí)行。。。");
}
}
第四步:編寫配置文件
<?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: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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 將目標(biāo)類納入到IOC容器 -->
<bean id="userService" class="com.lee.service.UserServiceImpl"></bean>
<!-- 將通知類納入到IOC容器 -->
<bean id="afterAdvice" class="com.lee.aop.AfterAdvice"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切入點(diǎn)(目標(biāo)方法) -->
<aop:pointcut expression="execution(* com.lee.spring.service.UserServiceImpl.impl.updateUser()) or execution(* com.lee.spring.service.UserServiceImpl.impl.deleteUser(int))" id="pointcut"/>
<!-- 配置顧問(將目標(biāo)方法和通知連接) -->
<aop:advisor advice-ref="afterAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
第五步:測(cè)試
@Test
public void test02() {
//1.加載配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 獲取bean
UserService userService = (UserService)context.getBean("userService");
userService.addUser();
System.out.println("-----------");
userService.deleteUser(1);
System.out.println("-------------");
userService.updateUser();
}
輸出:
addUser...
-----------
deleteUser...
返回值:true
method方法名:deleteUser
method參數(shù)個(gè)數(shù):1
method返回值類型:boolean
目標(biāo)對(duì)象:com.lee.service.UserServiceImpl@fdefd3f
后置通知執(zhí)行。。。
-------------
updateUser...
返回值:null
method方法名:updateUser
method參數(shù)個(gè)數(shù):0
method返回值類型:void
目標(biāo)對(duì)象:com.lee.service.UserServiceImpl@fdefd3f
后置通知執(zhí)行。。。
2.3 異常通知
異常通知需要實(shí)現(xiàn)ThrowsAdvice接口。
第一步:導(dǎo)入jar
aopalliance.jar
aspectjweaver.jar
第二步:編寫目標(biāo)類
- UserService.java
public interface UserService {
void addUser();
void updateUser();
boolean deleteUser(int no);
}
- UserServiceImpl.java
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("addUser...");
}
@Override
public void updateUser() {
System.out.println("updateUser...");
}
@Override
public boolean deleteUser(int no) {
System.out.println("deleteUser...");
return true;
}
}
第三步:編寫通知類
public class ExceptionAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
System.out.println("method方法名:" + method.getName());
System.out.println("method方法參數(shù)個(gè)數(shù):" + method.getParameterCount());
System.out.println("method方法默認(rèn)值:" + method.getDefaultValue());
System.out.println("args" +Arrays.toString( args));
System.out.println("目標(biāo)對(duì)象:" + target);
System.out.println("異常信息:" + ex.getMessage());
}
}
我們發(fā)現(xiàn)ThrowsAdvice接口中沒有聲明方法,但是文檔中有提示,告訴我們必須寫一個(gè)這樣的方法:

第四步:編寫配置文件
<?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: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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 將目標(biāo)類納入到IOC容器 -->
<bean id="userService" class="com.lee.service.UserServiceImpl"></bean>
<!-- 將通知類納入到IOC容器 -->
<bean id="excetionAdvice" class="com.lee.aop.ExceptionAdvice"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切入點(diǎn)(目標(biāo)方法) -->
<aop:pointcut expression="execution(* com.lee.service.UserServiceImpl.updateUser()) or execution(* com.lee.service.UserServiceImpl.deleteUser(int))" id="pointcut"/>
<!-- 配置顧問(將目標(biāo)方法和通知連接) -->
<aop:advisor advice-ref="excetionAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
我們將updateUser和deleteUser方法配置了增強(qiáng)。
第五步:測(cè)試
@Test
public void testBeforeAdvice() {
//1.加載配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 獲取bean
UserService userService = (UserService)context.getBean("userService");
userService.addUser();
System.out.println("-----------");
userService.deleteUser(1);
System.out.println("-------------");
userService.updateUser();
}
輸出:
addUser...
-----------
deleteUser...
-------------
updateUser...
發(fā)現(xiàn),增強(qiáng)沒有起作用。這是由于切入點(diǎn)(目標(biāo)方法)沒有異常,所以異常通知不會(huì)觸發(fā)。
修改目標(biāo)方法:給UserServiceImpl類中deleteUser()方法制造一個(gè)異常
@Override
public boolean deleteUser(int no) {
int i = 1/0;
System.out.println("deleteUser...");
return true;
}
輸出:
addUser...
-----------
method方法名:deleteUser
method方法參數(shù)個(gè)數(shù):1
method方法默認(rèn)值:null
args[1]
目標(biāo)對(duì)象:com.lee.service.UserServiceImpl@11dc3715
異常信息:/ by zero
發(fā)現(xiàn),異常通知觸發(fā),而且程序也拋出了異常,并且終止在發(fā)生異常的代碼處,后面的代碼不會(huì)繼續(xù)執(zhí)行。
2.4 環(huán)繞通知
看名字就知道環(huán)繞通知是環(huán)繞著目標(biāo)方法的,既然是環(huán)繞,所有他能實(shí)現(xiàn)前置通知、后置通知和異常通知。
環(huán)繞通知需要實(shí)現(xiàn)MethodInterceptor接口。
環(huán)繞通知的本質(zhì)是攔截器。
下面看一下案例實(shí)現(xiàn):
第一步:導(dǎo)入jar
aopalliance.jar
aspectjweaver.jar
第二步:編寫目標(biāo)類
- UserService.java
public interface UserService {
void addUser();
void updateUser();
boolean deleteUser(int no);
}
- UserServiceImpl.java
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("addUser...");
}
@Override
public void updateUser() {
System.out.println("updateUser...");
}
@Override
public boolean deleteUser(int no) {
System.out.println("deleteUser...");
return true;
}
}
第三步:編寫通知類RoundAdvice.java
public class RoundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
/*
* invocation里包含著目標(biāo)方法的全部信息
* 包括:方法名、參數(shù)個(gè)數(shù)、參數(shù)值、返回值。。。。
* 甚至可以控制目標(biāo)方法是否執(zhí)行
*/
Object result = null;
try {
//1.invocation.proceed()之前的代碼是前置通知
System.out.println("環(huán)繞通知實(shí)現(xiàn)的前置通知。。。");
System.out.println("目標(biāo)方法名:" + invocation.getMethod().getName());
result = invocation.proceed();//控制目標(biāo)方法的執(zhí)行
//2.invocation.proceed()之后的代碼是后置通知
System.out.println("環(huán)繞通知實(shí)現(xiàn)的后置通知。。。");
System.out.println("目標(biāo)對(duì)象:" + invocation.getThis());
System.out.println("返回值:" + result);
}catch (Exception e) {
//3.異常通知
System.out.println("環(huán)繞通知實(shí)現(xiàn)的異常通知。。。");
}
/*
* 返回值是目標(biāo)方法的返回值
* 由于環(huán)繞通知擁有目標(biāo)方法的絕對(duì)控制權(quán),他甚至可以偷梁換柱:將目標(biāo)方法的返回值進(jìn)行更改
*/
return result;
}
}
第四步:編寫配置文件
<?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: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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 將目標(biāo)類納入到IOC容器 -->
<bean id="userService" class="com.lee.service.UserServiceImpl"></bean>
<!-- 將通知類納入到IOC容器 -->
<bean id="roundAdvice" class="com.lee.aop.RoundAdvice"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切入點(diǎn)(目標(biāo)方法) -->
<aop:pointcut expression="execution(* com.lee.service.UserServiceImpl.deleteUser(int))" id="pointcut"/>
<!-- 配置顧問(將目標(biāo)方法和通知連接) -->
<aop:advisor advice-ref="roundAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
第五步:測(cè)試
@Test
public void test04() {
//1.加載配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 獲取bean
UserService userService = (UserService)context.getBean("userService");
userService.addUser();
System.out.println("-----------");
userService.deleteUser(1);
System.out.println("-------------");
userService.updateUser();
}
輸出:
addUser...
-----------
環(huán)繞通知實(shí)現(xiàn)的前置通知。。。
目標(biāo)方法名:deleteUser
deleteUser...
環(huán)繞通知實(shí)現(xiàn)的后置通知。。。
目標(biāo)對(duì)象:com.lee.service.UserServiceImpl@1a0dcaa
返回值:true
-------------
updateUser...
如果想查看環(huán)繞通知實(shí)現(xiàn)的異常通知,將目標(biāo)方法設(shè)計(jì)一個(gè)異常就可以了。
3. 基于注解的aop
將一個(gè)類變成有特定功能的類,有四種做法:
繼承類
實(shí)現(xiàn)接口
注解
配置文件
下面我們說一下用注解方式實(shí)現(xiàn)的的aop:
3.1 前置通知
① 實(shí)現(xiàn)步驟
第一步:導(dǎo)入jar包
- aspectjweaver.jar
第二步:編寫業(yè)務(wù)類
- UserService.java
public interface UserService {
void addUser(User student);
void updateUser(User student);
boolean deleteUser(int no);
}
- UserServiceImpl
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void addUser(User user) {
System.out.println("addUser..." + user);
}
@Override
public void updateUser(User user) {
System.out.println("updateUser..." + user);
}
@Override
public boolean deleteUser(int no) {
System.out.println("deleteUser...學(xué)號(hào)是:" + no );
return true;
}
}
第三步:編寫通知類AnnotationAdvice.java
@Component
@Aspect
public class AnnotationAdvice {
@Before("execution(* com.lee.service.*.addUser(..))")
public void before() {
System.out.println("前置通知執(zhí)行。。。");
}
}
在類上加注解@Aspect,就表示把普通的類變成了通知類。
在方法上加注解@Before,就表示把普通的方法變成了前置方法。@Before里面需要些切入點(diǎn)的表達(dá)式。
第四步:編寫配置文件
<?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: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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 開啟包掃描 -->
<context:component-scan base-package="com.lee"></context:component-scan>
<!-- 開啟aop支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
如果要用注解開發(fā)aop,配置文件里必須開啟對(duì)aop的支持。
第五步:測(cè)試
@Test
public void testBeforeAdvice() {
//1.加載配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 獲取bean
UserService userService = (UserService)context.getBean("userService");
User user = new User(1001, "zs");
userService.addUser(user);
System.out.println("-----------");
userService.deleteUser(1);
System.out.println("-------------");
userService.updateUser(user);
}
輸出:
前置通知執(zhí)行。。。
addUser...User [no=1001, name=zs]
-----------
deleteUser...學(xué)號(hào)是:1
-------------
updateUser...User [no=1001, name=zs]
② 注意事項(xiàng)
普通的類要變成通知類,需要在類上加注解@Aspect
通知類里面的方法要變成前置通知方法,需要在方法上@Before注解,而且注解里面需要配置切入點(diǎn)表達(dá)式。
如果想讓注解配置的aop生效,需要在配置文件中開啟對(duì)aop的支持。
此案例我們將目標(biāo)類和通知類加入到IOC容器的方式是:注解掃描方式。也可以用xml配置方式。
③ JoinPoint
怎么可以像實(shí)現(xiàn)接口那樣,獲取到一些關(guān)于切入點(diǎn)的相關(guān)信息呢?用JoinPoint。
注意是org.aspectj.lang.JoinPoint。
通知類:
@Component
@Aspect
public class AnnotationAdvice {
@Before("execution(* com.lee.service.*.addUser(..))")
public void before(JoinPoint jp) {
System.out.println("目標(biāo)方法相關(guān)信息:" + jp.getSignature());
System.out.println("目標(biāo)方法名:" + jp.getSignature().getName());
System.out.println( "方法參數(shù):" + Arrays.toString(jp.getArgs()));
System.out.println("目標(biāo)對(duì)象:" + jp.getThis());
System.out.println("前置通知執(zhí)行。。。");
}
}
輸出:
目標(biāo)方法簽名:void com.lee.service.UserService.addUser(User)
目標(biāo)方法名:addUser
方法參數(shù):[User [no=1001, name=zs]]
目標(biāo)對(duì)象:com.lee.service.UserServiceImpl@7c7b252e
前置通知執(zhí)行。。。
addUser...User [no=1001, name=zs]
-----------
deleteUser...學(xué)號(hào)是:1
-------------
updateUser...User [no=1001, name=zs]
④ 相關(guān)方法
| 方法 | 解釋 |
|---|---|
| getSignature() | 獲取目標(biāo)方法相關(guān)信息 |
| getSignature().getName() | 獲取目標(biāo)方法名 |
| getArgs() | 獲取目標(biāo)方法參數(shù) |
| getThis() | 獲取目標(biāo)對(duì)象 |
| getTarget() | 獲取目標(biāo)對(duì)象 |
3.2 后置通知
后置通知的注解是@AfterReturning
@Component
@Aspect
public class AnnotationAdvice {
@AfterReturning( pointcut = "execution(* com.lee.service.*.deleteUser(..))" , returning = "returnVal")
public void after(JoinPoint jp , Object returnVal) {
System.out.println("目標(biāo)方法相關(guān)信息:" + jp.getSignature());
System.out.println("目標(biāo)方法名:" + jp.getSignature().getName());
System.out.println( "方法參數(shù):" + Arrays.toString(jp.getArgs()));
System.out.println("目標(biāo)對(duì)象:" + jp.getThis());
System.out.println("返回值:" + returnVal);
System.out.println("后置通知執(zhí)行。。。");
}
}
注意:
如果想查看返回值的回話,需要在@AfterReturning里聲明returning = "xxx"。
@AfterReturning( pointcut = "execution(* com.lee.service.*.deleteUser(..))" , returning = "returnVal")中,既可以用 pointcut = "xxx",也可以用 value = "xxx"。
3.3 異常通知
異常通知用注解@AfterThrowing。
如果需要打印異常信息需要在@AfterThrowing里加一個(gè)throwing = "xxx"
@Component
@Aspect
public class AnnotationAdvice {
@AfterThrowing(pointcut = "execution(* com.lee.service.*.addUser(..))" , throwing = "th")
public void myException(JoinPoint jp , Exception th) {
System.out.println("目標(biāo)方法相關(guān)信息:" + jp.getSignature());
System.out.println("目標(biāo)方法名:" + jp.getSignature().getName());
System.out.println( "方法參數(shù):" + Arrays.toString(jp.getArgs()));
System.out.println("目標(biāo)對(duì)象:" + jp.getThis());
System.out.println("異常信息:" + th.getMessage());
System.out.println("異常通知執(zhí)行。。。");
}
}
輸出:
目標(biāo)方法相關(guān)信息:void com.lee.service.UserService.addUser(User)
目標(biāo)方法名:addUser
方法參數(shù):[User [no=1001, name=zs]]
目標(biāo)對(duì)象:com.lee.service.UserServiceImpl@48f2bd5b
異常信息:/ by zero
異常通知執(zhí)行。。。
擴(kuò)展:
上面方法捕獲的異常級(jí)別是Exception,如果我只想捕獲特定異常,比如:ArithmeticException。當(dāng)程序產(chǎn)生其他異常時(shí),就會(huì)捕捉不到。
@Aspect
public class AnnotationAdvice {
@AfterThrowing(pointcut = "execution(* com.lee.service.*.addUser(..))" , throwing = "th")
public void myException(JoinPoint jp , NullPointerException th) {
System.out.println("目標(biāo)方法相關(guān)信息:" + jp.getSignature());
System.out.println("目標(biāo)方法名:" + jp.getSignature().getName());
System.out.println( "方法參數(shù):" + Arrays.toString(jp.getArgs()));
System.out.println("目標(biāo)對(duì)象:" + jp.getThis());
System.out.println("異常信息:" + th.getMessage());
System.out.println("異常通知執(zhí)行。。。");
}
}
異常信息就沒有被捕獲到,是由于程序中異常通知僅僅捕獲的是NullPointerException,而目標(biāo)方法里面出現(xiàn)的異常時(shí)ArithmeticException,所以沒有捕獲到
3.4 最終通知
最終通知是無論程序正常執(zhí)行還是異常執(zhí)行,都會(huì)執(zhí)行的一個(gè)通知。類似于try、catch、finally里面的finally。
最終通知用的注解是@After。
@Component
@Aspect
public class AnnotationAdvice {
@After("execution(* com.lee.service.*.addUser(..))" )
public void zuizhong() {
System.out.println("最終通知執(zhí)行。。。");
}
}
3.5 環(huán)繞通知
環(huán)繞通知用的注解是@Around
@Component
@Aspect
public class AnnotationAdvice {
@Around(value = "execution(* com.lee.service.*.addUser(..))")
//獲取目標(biāo)對(duì)象的詳細(xì)信息不能再用JoinPoint,需要用子接口ProceedingJoinPoint。
public void around(ProceedingJoinPoint jp) {
try {
//執(zhí)行目標(biāo)方法之前,前置通知
System.out.println("前置通知執(zhí)行。。。");
jp.proceed();//執(zhí)行目標(biāo)方法
//執(zhí)行目標(biāo)方法之后,后置通知
System.out.println("后置通知執(zhí)行。。。");
}catch (Throwable e) {
System.out.println("異常通知執(zhí)行。。。");
}finally {
System.out.println("最終通知執(zhí)行。。。");
}
}
}
目標(biāo)方法沒有異常,輸出:
前置通知執(zhí)行。。。
addUser...User [no=1001, name=zs]
后置通知執(zhí)行。。。
最終通知執(zhí)行。。。
目標(biāo)方法存在異常,輸出:
前置通知執(zhí)行。。。
addUser...User [no=1001, name=zs]
異常通知執(zhí)行。。。
最終通知執(zhí)行。。。
執(zhí)行順序
-
目標(biāo)方法沒有異常:
前置通知-->目標(biāo)方法-->后置通知-->最終通知
-
目標(biāo)方法存在異常:
前置通知-->目標(biāo)方法-->異常通知-->最終通知
注意事項(xiàng)
獲取目標(biāo)對(duì)象的詳細(xì)信息不能再用JoinPoint,需要用子接口ProceedingJoinPoint。
catch塊里異常信息的捕獲級(jí)別必須是Throwable,不能是其他。
如果目標(biāo)方法沒有返回值,則環(huán)繞通知方法的返回值也可以是void。如果目標(biāo)方法有返回值,則環(huán)繞通知方法也必須有返回值,否則會(huì)報(bào)錯(cuò):Null return value from advice does not match primitive return type for: ....
@Aspect
public class AnnotationAdvice {
@Around(value = "execution(* com.lee.service.*.deleteUser(..))")
public Object around(ProceedingJoinPoint jp) {
Object result = null ;
try {
//執(zhí)行目標(biāo)方法之前,前置通知
System.out.println("前置通知執(zhí)行。。。");
result = jp.proceed();//執(zhí)行目標(biāo)方法
//執(zhí)行目標(biāo)方法之后,后置通知
System.out.println("后置通知執(zhí)行。。。");
}catch (Throwable e) {
System.out.println("異常通知執(zhí)行。。。");
}finally {
System.out.println("最終通知執(zhí)行。。。");
}
return result;
}
}
4. AOP底層實(shí)現(xiàn)原理
AOP的底層原理實(shí)現(xiàn)就是Spring的動(dòng)態(tài)代理。
4.1 Spring創(chuàng)建的動(dòng)態(tài)代理類在哪?
Spring框架在運(yùn)?時(shí),通過動(dòng)態(tài)字節(jié)碼技術(shù),在JVM中創(chuàng)建的,運(yùn)?在JVM內(nèi)部,等程序結(jié)束后,會(huì)和 JVM ?起消失。
-
什么叫動(dòng)態(tài)字節(jié)碼技術(shù):
動(dòng)態(tài)字節(jié)碼技術(shù)
JVM是怎么創(chuàng)建對(duì)象的?
首先需要編寫 .java 的源文件,源文件編譯后會(huì)變成 .class的字節(jié)碼文件,JVM通過加載字節(jié)碼就能創(chuàng)建出這個(gè)類的對(duì)象。
那什么是動(dòng)態(tài)字節(jié)碼?
動(dòng)態(tài)字節(jié)碼就意味著不需要編寫 .java的源文件,也就不能編譯為 .class文件。但是虛擬機(jī)創(chuàng)建對(duì)象肯定需要的還是 .class格式的字節(jié)碼,此時(shí)的字節(jié)碼就是動(dòng)態(tài)創(chuàng)建的。
動(dòng)態(tài)字節(jié)碼是怎么創(chuàng)建的?
通過第三方的動(dòng)態(tài)字節(jié)碼框架創(chuàng)建,常見的動(dòng)態(tài)字節(jié)碼框架有 ASM、Javaassist、Cglib。這些框架可以動(dòng)態(tài)的生成字節(jié)碼。
所以,動(dòng)態(tài)代理類的本質(zhì)就是動(dòng)態(tài)字節(jié)碼技術(shù),比如 UserServiceProxy就會(huì)自動(dòng)的通過動(dòng)態(tài)字節(jié)碼在JVM中生成,而不需要去手動(dòng)編寫。
-
結(jié)論:
動(dòng)態(tài)代理不需要定義類?件,都是JVM運(yùn)?過程中動(dòng)態(tài)創(chuàng)建的,所以不會(huì)造成靜態(tài)代理,類?件數(shù)量過多,影響項(xiàng)?管理的問題。
4.2 Spring工廠如何加工原始對(duì)象
- 思路分析:
就是使用后置bean去對(duì)原始對(duì)象進(jìn)行再加工,再加工階段加上額外功能。
再加工的過程使用的是動(dòng)態(tài)代理。

- 核心編碼
UserService.java接口:
package com.lee.factory;
public interface UserService {
void register();
void login();
}
UserServiceImpl.java 實(shí)現(xiàn)類:
package com.lee.factory;
public class UserServiceImpl implements UserService {
@Override
public void register() {
System.out.println("注冊(cè)。。。。。");
}
@Override
public void login() {
System.out.println("登錄。。。。");
}
}
將UserServiceImpl納入到IoC容器:
<bean id="userService" class="com.lee.factory.UserServiceImpl"></bean>
創(chuàng)建ProxyBeanPostProcessor.java,實(shí)現(xiàn)代理:
package com.lee.factory;
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String args) throws BeansException {
ClassLoader loader = ProxyBeanPostProcessor.class.getClassLoader();
Class<?>[] interfaces = bean.getClass().getInterfaces();
InvocationHandler invocationHandle = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---------log.....-----");
Object invoke = method.invoke(bean, args);
return invoke;
}
};
//使用JDK代理創(chuàng)建對(duì)象
Object ret = Proxy.newProxyInstance(loader, interfaces, invocationHandle);
return ret;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String args) throws BeansException {
return bean;
}
}
測(cè)試:
public class TestAopProxy {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-factory.xml");
UserService userService = (UserService)context.getBean("userService");
userService.register();
userService.login();
}
}
輸出:
---------log.....-----
注冊(cè)。。。。。
---------log.....-----
登錄。。。。
5. AOP開發(fā)中的?個(gè)坑
先看一個(gè)案例:
業(yè)務(wù)里有兩個(gè)方法,分別是a() 和 b():
package com.lee.spring.service.impl;
public class UserServiceImpl {
public void a() {
System.out.println("a方法執(zhí)行");
}
public void b() {
System.out.println("b方法執(zhí)行");
}
}
有一個(gè)額外方法,通過 Spring 進(jìn)行增強(qiáng):
package com.lee.spring.aop;
public class Before implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("前置增強(qiáng)執(zhí)行");
}
}
配置文件:
<?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-4.3.xsd">
<!-- 將UserService納入到IOC容器 -->
<bean id = "userService" class="com.lee.spring.service.impl.UserServiceImpl"></bean>
<!-- 將Before增強(qiáng)納入到IOC容器 -->
<bean id = "before" class="com.lee.spring.aop.Before"></bean>
<!-- 配置增強(qiáng) -->
<aop:config>
<aop:pointcut expression="execution(* *..UserServiceImpl.*(..))" id="pointcut"/>
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
</aop:config>
</beans>
編寫測(cè)試類進(jìn)行測(cè)試:
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userService = (UserServiceImpl)context.getBean("userService");
userService.a();
userService.b();
}
控制臺(tái)輸出:
前置增強(qiáng)執(zhí)行
a方法執(zhí)行
前置增強(qiáng)執(zhí)行
b方法執(zhí)行
以上操作都是正常的 aop 前置增強(qiáng),如果業(yè)務(wù)邏輯變?yōu)椋?/p>
package com.lee.spring.service.impl;
public class UserServiceImpl {
public void a() {
System.out.println("a方法執(zhí)行");
b();
}
public void b() {
System.out.println("b方法執(zhí)行");
}
}
測(cè)試變?yōu)椋?/p>
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userService = (UserServiceImpl)context.getBean("userService");
userService.a();
}
控制臺(tái)輸出:
前置增強(qiáng)執(zhí)行
a方法執(zhí)行
b方法執(zhí)行
很明顯,在方法a() 中 調(diào)用b()方法,b()方法的前置增強(qiáng)就不生效了。這就是aop中的一個(gè)坑。
原因分析:
在執(zhí)行 userService.a();時(shí),a()方法調(diào)用的時(shí)候userService對(duì)象,而userService對(duì)象是從ioc容器中獲取的代理對(duì)象,所以可以進(jìn)行增強(qiáng)。但是在 a() 方法中調(diào)用 b() 方法,b() 方法是來自與 userService對(duì)象本身,而不是 Spring代理的,所以不具備增強(qiáng)功能。
所以可以這樣改進(jìn):
package com.lee.spring.service.impl;
public class UserServiceImpl {
public void a() {
System.out.println("a方法執(zhí)行");
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userService = (UserServiceImpl)context.getBean("userService");
userService.b();
}
public void b() {
System.out.println("b方法執(zhí)行");
}
}
在調(diào)用 b() 方法的時(shí)候,不是直接調(diào)用,而是從ioc中獲取代理對(duì)象,然后調(diào)用代理對(duì)象的 b() 方法。
但是這樣有個(gè)問題:Spring工廠是重量級(jí)資源,會(huì)侵占內(nèi)存,所以一個(gè)應(yīng)用中只創(chuàng)建一個(gè)工廠就足夠了。
可以這樣更改:
package com.lee.spring.service.impl;
public class UserServiceImpl implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
public void a() {
System.out.println("a方法執(zhí)行");
UserServiceImpl userService = (UserServiceImpl)context.getBean("userService");
userService.b();
}
public void b() {
System.out.println("b方法執(zhí)行");
}
}
6. AOP階段知識(shí)總結(jié)


