AOP 關(guān)鍵術(shù)語
1.target:目標(biāo)類腰素,需要被代理的類书幕。例如:UserService
2.Joinpoint(連接點(diǎn)):所謂連接點(diǎn)是指那些可能被攔截到的方法阱持。例如:所有的方法
3.PointCut 切入點(diǎn):已經(jīng)被增強(qiáng)的連接點(diǎn)答恶。例如:addUser()
4.advice 通知/增強(qiáng)瀑粥,增強(qiáng)代碼挣跋。例如:after、before
5. Weaving(織入):是指把增強(qiáng)advice應(yīng)用到目標(biāo)對(duì)象target來創(chuàng)建新的代理對(duì)象proxy的 過程.
6.proxy 代理類:通知+切入點(diǎn)
7. Aspect(切面): 是切入點(diǎn)pointcut和通知advice的結(jié)合
具體可以根據(jù)下面這張圖來理解:
AOP 的通知類型
Spring按照通知Advice在目標(biāo)類方法的連接點(diǎn)位置狞换,可以分為5類
前置通知
org.springframework.aop.MethodBeforeAdvice
在目標(biāo)方法執(zhí)行前實(shí)施增強(qiáng)避咆,比如上面例子的 before()方法
后置通知
org.springframework.aop.AfterReturningAdvice
在目標(biāo)方法執(zhí)行后實(shí)施增強(qiáng)舟肉,比如上面例子的 after()方法
環(huán)繞通知
org.aopalliance.intercept.MethodInterceptor
在目標(biāo)方法執(zhí)行前后實(shí)施增強(qiáng)
異常拋出通知
org.springframework.aop.ThrowsAdvice
在方法拋出異常后實(shí)施增強(qiáng)
引介通知
org.springframework.aop.IntroductionInterceptor
在目標(biāo)類中添加一些新的方法和屬性
我們只需要在Spring 的配置文件 applicationContext.xml 進(jìn)行如下配置
<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">
<!--1、 創(chuàng)建目標(biāo)類 -->
<bean id="userService" class="com.my.aop.UserServiceImpl"></bean>
<!--2牌借、創(chuàng)建切面類(通知) -->
<bean id="transaction" class="com.my.aop.one.MyTransaction"></bean>
<!--3度气、aop編程
3.1 導(dǎo)入命名空間
3.2 使用 <aop:config>進(jìn)行配置
proxy-target-class="true" 聲明時(shí)使用cglib代理
如果不聲明,Spring 會(huì)自動(dòng)選擇cglib代理還是JDK動(dòng)態(tài)代理
<aop:pointcut> 切入點(diǎn) 膨报,從目標(biāo)對(duì)象獲得具體方法
<aop:advisor> 特殊的切面磷籍,只有一個(gè)通知 和 一個(gè)切入點(diǎn)
advice-ref 通知引用
pointcut-ref 切入點(diǎn)引用
3.3 切入點(diǎn)表達(dá)式
execution(* com.my.aop.*.*(..))
選擇方法 返回值任意 包 類名任意 方法名任意 參數(shù)任意
-->
<aop:config>
<!-- 切入點(diǎn)表達(dá)式 -->
<aop:pointcut expression="execution(* com.my.aop.*.*(..))" id="myPointCut"/>
<aop:aspect ref="transaction">
<!-- 配置前置通知,注意 method 的值要和 對(duì)應(yīng)切面的類方法名稱相同 -->
<aop:before method="before" pointcut-ref="myPointCut"></aop:before>
<aop:after-returning method="after" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService useService = (UserService) context.getBean("userService");
useService.addUser(null);
}
∠帜①院领、 切入點(diǎn)表達(dá)式,一個(gè)完整的方法表示如下:
execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
類修飾符 返回值 方法所在的包 方法名 方法拋出的異常
那么根據(jù)上面的對(duì)比够吩,我們就很好理解:
execution(* com.my.aop.*.*(..))
選擇方法 返回值任意 包 類名任意 方法名任意 參數(shù)任意
那么它表達(dá)的意思是 返回值任意比然,包名為 com.ys.aop 下的任意類名中的任意方法名,參數(shù)任意周循。
如果切入點(diǎn)表達(dá)式有多個(gè)不同目錄呢强法?
<aop:pointcut expression="execution(* com.my.*Service1.*(..)) ||
execution(* com.my.*Service2.*(..))" id="myPointCut"/>
表示匹配 com.my包下的,以 Service1結(jié)尾或者以Service2結(jié)尾的類的任意方法湾笛。
AOP 切入點(diǎn)表達(dá)式支持多種形式的定義規(guī)則:
1饮怯、execution:匹配方法的執(zhí)行(常用)
execution(public *.*(..))
2.within:匹配包或子包中的方法(了解)
within(com.my.aop..*)
3.this:匹配實(shí)現(xiàn)接口的代理對(duì)象中的方法(了解)
this(com.my.aop.user.UserDAO)
4.target:匹配實(shí)現(xiàn)接口的目標(biāo)對(duì)象中的方法(了解)
target(com.my.aop.user.UserDAO)
5.args:匹配參數(shù)格式符合標(biāo)準(zhǔn)的方法(了解)
args(int,int)
6.bean(id) 對(duì)指定的bean所有的方法(了解)
bean('userServiceId')
②、springAOP 的具體加載步驟:
1嚎研、當(dāng) spring 容器啟動(dòng)的時(shí)候蓖墅,加載了 spring 的配置文件
2、為配置文件中的所有 bean 創(chuàng)建對(duì)象
3临扮、spring 容器會(huì)解析 aop:config 的配置
1论矾、解析切入點(diǎn)表達(dá)式,用切入點(diǎn)表達(dá)式和納入 spring 容器中的 bean 做匹配
如果匹配成功杆勇,則會(huì)為該 bean 創(chuàng)建代理對(duì)象贪壳,代理對(duì)象的方法=目標(biāo)方法+通知
如果匹配不成功,不會(huì)創(chuàng)建代理對(duì)象
4蚜退、在客戶端利用 context.getBean() 獲取對(duì)象時(shí)闰靴,如果該對(duì)象有代理對(duì)象,則返回代理對(duì)象关霸;如果沒有传黄,則返回目標(biāo)對(duì)象
說明:如果目標(biāo)類沒有實(shí)現(xiàn)接口杰扫,則 spring 容器會(huì)采用 cglib 的方式產(chǎn)生代理對(duì)象队寇,如果實(shí)現(xiàn)了接口,則會(huì)采用 jdk 的方式
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class StudentServiceAspect {
public void doBefore(JoinPoint jp){
System.out.println("類名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("開始添加學(xué)生:"+jp.getArgs()[0]);
}
public void doAfter(JoinPoint jp){
System.out.println("類名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("學(xué)生添加完成:"+jp.getArgs()[0]);
}
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("添加學(xué)生前");
Object retVal=pjp.proceed();
System.out.println(retVal);
System.out.println("添加學(xué)生后");
return retVal;
}
public void doAfterReturning(JoinPoint jp){
System.out.println("返回通知");
}
public void doAfterThrowing(JoinPoint jp,Throwable ex){
System.out.println("異常通知");
System.out.println("異常信息:"+ex.getMessage());
}
}
<?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="studentServiceAspect" class="com.java.advice.StudentServiceAspect"></bean>
<bean id="studentService" class="com.java.service.impl.StudentServiceImpl"></bean>
<aop:config>
<aop:aspect id="studentServiceAspect" ref="studentServiceAspect">
<aop:pointcut expression="execution(* com.java.service.*.*(..))" id="businessService"/>
<aop:before method="doBefore" pointcut-ref="businessService"/>
<aop:after method="doAfter" pointcut-ref="businessService"/>
<aop:around method="doAround" pointcut-ref="businessService"/>
<aop:after-returning method="doAfterReturning" pointcut-ref="businessService"/>
<aop:after-throwing method="doAfterThrowing" pointcut-ref="businessService" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
public class StudentServiceImpl implements StudentService{
@Override
public void addStudent(String name) {
// System.out.println("開始添加學(xué)生"+name);
System.out.println("添加學(xué)生"+name);
System.out.println(1/0);
// System.out.println("完成學(xué)生"+name+"的添加");
}
}
AspectJ是一個(gè)面向切面的框架章姓,它擴(kuò)展了Java語言佳遣。
Aspect 通知類型识埋,定義了類型名稱以及方法格式。類型如下:
before:前置通知(應(yīng)用:各種校驗(yàn))
在方法執(zhí)行前執(zhí)行零渐,如果通知拋出異常窒舟,阻止方法運(yùn)行
afterReturning:后置通知(應(yīng)用:常規(guī)數(shù)據(jù)處理)
方法正常返回后執(zhí)行,如果方法中拋出異常诵盼,通知無法執(zhí)行
必須在方法執(zhí)行后才執(zhí)行惠豺,所以可以獲得方法的返回值。
around:環(huán)繞通知(應(yīng)用:十分強(qiáng)大风宁,可以做任何事情)
方法執(zhí)行前后分別執(zhí)行洁墙,可以阻止方法的執(zhí)行
必須手動(dòng)執(zhí)行目標(biāo)方法
afterThrowing:拋出異常通知(應(yīng)用:包裝異常信息)
方法拋出異常后執(zhí)行,如果方法沒有拋出異常戒财,無法執(zhí)行
after:最終通知(應(yīng)用:清理現(xiàn)場(chǎng))
方法執(zhí)行完畢后執(zhí)行热监,無論方法中是否出現(xiàn)異常
這里最重要的是around,環(huán)繞通知,它可以代替上面的任意通知饮寞。
在程序中表示的意思如下:
try{
//前置:before
//手動(dòng)執(zhí)行目標(biāo)方法
//后置:afterRetruning
}?catch(){
//拋出異常 afterThrowing
}?finally{
//最終 after
}
AOP具體實(shí)例
public?interface?UserService?{
//添加 user
public?void?addUser();
//刪除 user
public?void?deleteUser();
}
創(chuàng)建實(shí)現(xiàn)類
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("增加 User");
}
@Override
public void deleteUser() {
System.out.println("刪除 User");
}
}
創(chuàng)建切面類(包含各種通知)
import org.aspectj.lang.JoinPoint;
public class MyAspect {
/**
* JoinPoint 能獲取目標(biāo)方法的一些基本信息
* @param joinPoint
*/
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知 : " + joinPoint.getSignature().getName());
}
public void myAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
}
public void myAfter(){
System.out.println("最終通知");
}
}
創(chuàng)建spring配置文件applicationContext.xml
<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">
<!--1孝扛、 創(chuàng)建目標(biāo)類 -->
<bean id="userService" class="com.my.aop.UserServiceImpl"></bean>
<!--2、創(chuàng)建切面類(通知) -->
<bean id="myAspect" class="com.my.aop.MyAspect"></bean>
<!--3幽崩、aop編程
3.1 導(dǎo)入命名空間
3.2 使用 <aop:config>進(jìn)行配置
proxy-target-class="true" 聲明時(shí)使用cglib代理
如果不聲明苦始,Spring 會(huì)自動(dòng)選擇cglib代理還是JDK動(dòng)態(tài)代理
<aop:pointcut> 切入點(diǎn) ,從目標(biāo)對(duì)象獲得具體方法
<aop:advisor> 特殊的切面歉铝,只有一個(gè)通知 和 一個(gè)切入點(diǎn)
advice-ref 通知引用
pointcut-ref 切入點(diǎn)引用
3.3 切入點(diǎn)表達(dá)式
execution(* com.my.aop.*.*(..))
選擇方法 返回值任意 包 類名任意 方法名任意 參數(shù)任意
-->
<aop:config>
<aop:aspect ref="myAspect">
<!-- 切入點(diǎn)表達(dá)式 -->
<aop:pointcut expression="execution(* com.my.aop.*.*(..))" id="myPointCut"/>
<!-- 3.1 前置通知
<aop:before method="" pointcut="" pointcut-ref=""/>
method : 通知盈简,及方法名
pointcut :切入點(diǎn)表達(dá)式,此表達(dá)式只能當(dāng)前通知使用太示。
pointcut-ref : 切入點(diǎn)引用柠贤,可以與其他通知共享切入點(diǎn)。
通知方法格式:public void myBefore(JoinPoint joinPoint){
參數(shù)1:
org.aspectj.lang.JoinPoint 用于描述連接點(diǎn)(目標(biāo)方法)类缤,獲得目標(biāo)方法名等
-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 3.2后置通知 ,目標(biāo)方法后執(zhí)行臼勉,獲得返回值
<aop:after-returning method="" pointcut-ref="" returning=""/>
returning 通知方法第二個(gè)參數(shù)的名稱
通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
參數(shù)1:連接點(diǎn)描述
參數(shù)2:類型Object,參數(shù)名 returning="ret" 配置的
-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />
<!-- 3.3 最終通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService useService = (UserService) context.getBean("userService");
useService.addUser();
}
測(cè)試異常通知
public
?void?addUser() {
int?i =?1/0;//顯然這里會(huì)拋出除數(shù)不能為 0
System.out.println("增加 User");
}
切面類
public?void?myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("拋出異常通知 : "?+ e.getMessage());
}
<!-- 3.4 拋出異常
<aop:after-throwing method="" pointcut-ref="" throwing=""/>
throwing :通知方法的第二個(gè)參數(shù)名稱
通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
參數(shù)1:連接點(diǎn)描述對(duì)象
參數(shù)2:獲得異常信息餐弱,類型Throwable 宴霸,參數(shù)名由throwing="e" 配置
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
@Test
public void testAop(){
String str = "com/my/execption/applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(str);
UserService useService = (UserService) context.getBean("userService");
useService.addUser();
}
測(cè)試環(huán)繞通知
public class MyAspect {
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("前置通知");
//手動(dòng)執(zhí)行目標(biāo)方法
Object obj = joinPoint.proceed();
System.out.println("后置通知");
return obj;
}
}
applicationContext.xml 配置如下:
通過 xml 配置的方式
<!-- 環(huán)繞通知
<aop:around method="" pointcut-ref=""/>
通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
返回值類型:Object
方法名:任意
參數(shù):
org.aspectj.lang.ProceedingJoinPoint
拋出異常
執(zhí)行目標(biāo)方法:Object obj = joinPoint.proceed();
-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
@Test
public void testAop(){
String str = "com/my/around/applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(str);
UserService useService = (UserService) context.getBean("userService");
useService.addUser();
}
注解實(shí)現(xiàn) AOP
@Aspect 聲明切面,修飾切面類膏蚓,從而獲得 通知瓢谢。
通知
@Before 前置
@AfterReturning 后置
@Around 環(huán)繞
@AfterThrowing 拋出異常
@After 最終
切入點(diǎn)
@PointCut ,修飾方法 private void xxx(){} 之后通過“方法名”獲得切入點(diǎn)引用
在 applicationContext.xml 文件中導(dǎo)入相應(yīng)的命名空間
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
注解配置 bean
xml配置:
<!--1驮瞧、創(chuàng)建目標(biāo)類 -->
<bean id="userService" class="com.my.aop.UserServiceImpl"></bean>
<!--2氓扛、創(chuàng)建切面類(通知) -->
<bean id="myAspect" class="com.my.aop.MyAspect"></bean>
配置掃描注解識(shí)別
在 applicationContext.xml 文件中添加如下配置:
<!-- 配置掃描注解類
base-package:表示含有注解類的包名。
如果掃描多個(gè)包,則下面的代碼書寫多行采郎,改變 base-package 里面的內(nèi)容即可千所!
-->
<context:component-scan base-package="com.my.aop"></context:component-scan>
在切面類上添加 @Aspect 注解,如下:
告訴 Spring 哪個(gè)是切面類
如何讓 Spring 認(rèn)識(shí)我們所配置的 AOP 注解呢
我們?cè)?applicationContext.xml 文件中增加如下配置:
<!--2蒜埋、確定 aop 注解生效 -->
<aop:aspectj-autoproxy>
注解配置前置通知
我們先看 xml 配置前置通知如下:
<!-- 切入點(diǎn)表達(dá)式 -->
<aop:pointcut expression="execution(* com.my.aop.*.*(..))" id="myPointCut"/>
<!-- 3.1 前置通知
<aop:before method="" pointcut="" pointcut-ref=""/>
method : 通知淫痰,及方法名
pointcut :切入點(diǎn)表達(dá)式,此表達(dá)式只能當(dāng)前通知使用整份。
pointcut-ref : 切入點(diǎn)引用待错,可以與其他通知共享切入點(diǎn)。
通知方法格式:public void myBefore(JoinPoint joinPoint){
參數(shù)1:
org.aspectj.lang.JoinPoint 用于描述連接點(diǎn)(目標(biāo)方法)烈评,獲得目標(biāo)方法名等
-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
那么注解的方式如下:
注解配置后置通知
xml 配置后置通知:
<!-- 3.2后置通知 ,目標(biāo)方法后執(zhí)行朗鸠,獲得返回值
<aop:after-returning method="" pointcut-ref="" returning=""/>
returning 通知方法第二個(gè)參數(shù)的名稱
通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
參數(shù)1:連接點(diǎn)描述
參數(shù)2:類型Object,參數(shù)名 returning="ret" 配置的
-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />
注意看础倍,后置通知有個(gè) returning="ret" 配置烛占,這是用來獲得目標(biāo)方法的返回值的。
public void testAopAnnotation(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext_Annotation.xml");
UserService useService = (UserService) context.getBean("userService");
useService.addUser();
useService.deleteUser();
}