簡介
AOP的原理:基于代理思想潘酗,對原來的目標對象船响,創(chuàng)建代理對象。在不修改原對象代碼情況下袖迎,通過代理對象季眷,調(diào)用增加功能的代碼余蟹,對原有業(yè)務方法進行增強。
應用場景:
- 記錄日志
- 監(jiān)控方法運行時間
- 權限控制
- 緩存優(yōu)化
- 事務管理
Spring AOP兩種編程方式
- Spring 1.2 開始支持AOP編程 (傳統(tǒng)SpringAOP 編程)
- Spring 2.0 之后支持第三方 AOP框架(AspectJ )
AOP 編程底層實現(xiàn)機制
Spring AOP 是基于動態(tài)代理的子刮,兩種方式
- JDK 動態(tài)代理
- CGLIB 動態(tài)代理
動態(tài)代理和靜態(tài)代理的區(qū)別威酒?
動態(tài)代理:在虛擬機內(nèi)部,運行的時候,動態(tài)生成代理類(運行時生成兼搏,runtime生成) 卵慰,并不是真正存在的類,Proxy$$(Proxy$$Customer)
靜態(tài)代理:存在代理類
JDK 動態(tài)代理 (對象必須有接口)
必須對接口生成代理
-
采用Proxy對象佛呻,通過newProxyInstance方法為目標創(chuàng)建代理對象
該方法接收三個參數(shù) :目標對象類加載器裳朋、目標對象實現(xiàn)的接口、代理后的處理程序 InvocationHandler
使用 Proxy類提供 newProxyInstance 方法對目標對象接口進行代理
實現(xiàn) InvocationHandler 接口中 invoke方法吓著,在目標對象每個方法調(diào)用時鲤嫡,都會執(zhí)行invoke
代碼
JdkProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyFactory implements InvocationHandler {
private Object target;
//構造注入
public JdkProxyFactory(Object target) {
this.target = target;
}
public Object getProxyObject() {
//匿名內(nèi)部內(nèi)
/* Object proxyObject = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass()
.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//....
return null;
}
});
return proxyObject;*/
/**
* loader - 定義代理類的類加載器
* interfaces - 目標對象的所有接口(原因:jdk要根據(jù)目標的接口來生成子對象)
* h - 回調(diào)函數(shù),代理對象要調(diào)用的方法绑莺。(可以寫增強的代碼)
*
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
//this代表當前實現(xiàn)類的對象, 實際上就相當于 new JdkProxyFactory()
}
/**
* @param proxy 代理對象(慎用,會遞歸)
* @param method 目標對象原來的方法
* @param args 目標對象的參數(shù)
*
* @return Object方法調(diào)用的返回值, 不是 method.incoke()這個返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("save".equals(method.getName())) {
//增強代碼
writeLog();
//執(zhí)行原來的代碼
return method.invoke(target, args);
}
return method.invoke(target, args);
}
private static void writeLog() {
System.out.println("增強了寫日志方法...");
}
/*
//內(nèi)部內(nèi)
private class MyInvocationHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//.....
return null;
}
}*/
}
Cglib 動態(tài)代理 (不需要接口也可以代理)
Cglib的引入為了解決類的直接代理問題(生成代理子類)暖眼,**不需要接口也可以代理 **
代碼
pom.xml
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAOP相關的坐標-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring整合單元測試-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--單元測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
CglibProxyFactory.java
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
public Object getProxyObject() {
//實例化Cglib增強代理器
Enhancer enhancer = new Enhancer();
//2.在增強器上設置相應的屬性
// 設置目標的類,通過目標對象生成代理的子對象
enhancer.setSuperclass(target.getClass());
//設置回調(diào)方法的函數(shù)
enhancer.setCallback(this);
//通過增強器,獲取帶代理對象
Object proxyObject = enhancer.create();
return proxyObject;
}
/**
* @param proxy 代理子對象
* @param method 目標的方法對象
* @param agrs 目標的方法的參數(shù)
* @param methodProxy 代理對象的方法(已經(jīng)被增強過的)
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] agrs, MethodProxy methodProxy) throws Throwable {
//增強代碼
System.out.println("增強了寫日志...");
//執(zhí)行目標對象之前的代碼
Object object = method.invoke(target, agrs);
return object;
}
}
jdk:基于接口的代理纺裁,會生成目標對象的接口類型的子對象诫肠。
cglib:基于類的代理,會生成目標對象類型的子對象欺缘。
總結:
- Spring AOP 優(yōu)先使用接口代理 (JDK動態(tài)代理)
- 如果沒有接口栋豫,使用Cglib動態(tài)代理
AspectJ 切面編程 (xml)
AOP 相關術語
需求: 在save()方法邏輯調(diào)用之前進行權限的校驗
Joinpoint 連接點:所有的方法都是連接點
Pointcut 切入點:就是需要增強的方法 eg:sava()方法需要增強,save()就是切入點
Advice 通知/增強:增強的邏輯 eg:權限的校驗邏輯
Target 目標對象
Weaving 織入
Proxy 代理對象
-
Aspect 切面:把通知和切入點進行結合,就是把通知的邏輯用在切入點谚殊。
換句話:對哪些方法進行怎樣的代碼增強
通知類型
- 前置通知 : 在目標方法調(diào)用之前調(diào)用 (權限校驗)
- 后置通知 : 在目標方法調(diào)用之后調(diào)用 returning 返回值
- 環(huán)繞通知 : 前置+后置 (事務) 在方法之前和方法之后執(zhí)行. 特點:可以阻止目標方法執(zhí)行
- 異常通知: 目標方法出現(xiàn)異常執(zhí)行. 如果方法沒有異常,不會執(zhí)行. 特點:可以獲得異常的信息
- 最終通知:指的是無論是否有異常丧鸯,總是被執(zhí)行的。
開發(fā)步驟
確定目標 bean (xml配置要增強的目標)
編寫 Advice通知 (普通的JavaBean即可嫩絮,不需實現(xiàn)接口)
配置aop: 切入點和切面
代碼
pom.xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Spring AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--Spring整合單元測試-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
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"
xmlns:context="http://www.springframework.org/schema/context"
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>
xml 中聲明 增強的bean (擴展的bean) 和 被增強的bean (被擴展的bean)
<!--注冊userService-->
<bean id="userService" class="perm.coco.service.Impl.UserServiceImpl"></bean>
<!--注冊切面類-->
<bean id="myAspect" class="perm.coco.aspectj.MyAspect"></bean>
aop 配置
<!--配置Aop,切入點和切面-->
<aop:config>
<!--切入點-->
<aop:pointcut id="pointcut01" expression="execution(* perm.coco.service.Impl.UserServiceImpl.save(..))"/>
<aop:pointcut id="pointcut02" expression="execution(* perm.coco.service.Impl.UserServiceImpl.delete(..))"/>
<aop:pointcut id="pointcut03" expression="execution(* perm.coco.service.Impl.UserServiceImpl.findAll(..))"/>
<aop:pointcut id="pointcut04" expression="execution(* perm.coco.service.Impl.*.*(..))"/>
<aop:pointcut id="pointcut05" expression="execution(* perm.coco.service.Impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--前置-->
<aop:before method="before" pointcut-ref="pointcut01"></aop:before>
<!--后置-->
<aop:after-returning method="after" pointcut-ref="pointcut02"></aop:after-returning>
<!--環(huán)繞-->
<aop:around method="around" pointcut-ref="pointcut03"></aop:around>
<!--異常通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut04" throwing="e"></aop:after-throwing>
<!--最終通知-->
<aop:after method="after" pointcut-ref="pointcut05"></aop:after>
</aop:aspect>
</aop:config>
前置通知
在原來方法執(zhí)行前執(zhí)行
應用: 權限控制 (權限不足丛肢,拋出異常)、 記錄方法調(diào)用信息日志
<!--配置Aop,切入點和切面-->
<aop:config>
<!--切入點-->
<aop:pointcut id="pointcut01" expression="execution(* perm.coco.service.Impl.UserServiceImpl.save(..))"/>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--前置-->
<aop:before method="before" pointcut-ref="pointcut01"></aop:before>
</aop:config>
//前置
public void before() {
System.out.println("前置增強了...");
}
后置通知
<!--后置-->
<aop:after-returning method="after" pointcut-ref="pointcut02"></aop:after-returning>
//后置
public void afterReturning() {
System.out.println("后置打印日志....");
}
....待續(xù)....