概述
① AOP(Aspect Orient Programming), 面向切面編程,AOP 是一種編程思想,是面向?qū)ο缶幊蹋∣OP)的一種補充;
② 面向切面編程,不修改源代碼給程序動態(tài)統(tǒng)一添加額外功能的一種技術(shù),實現(xiàn)業(yè)務(wù)代碼解耦;
③ 應(yīng)用主要體現(xiàn)在事務(wù)處理、日志管理、權(quán)限控制、異常處理等方面,分離功能性需求和非功能性需求,增強代碼的可讀性和可維護(hù)性。
graph LR
日志記錄---事務(wù)管理---權(quán)限驗證---性能監(jiān)控
靜態(tài)代理
Spring Aop底層實現(xiàn)原理是動態(tài)代理,因此先介紹一下靜態(tài)代理。
特點
① 靜態(tài)代理要求目標(biāo)對象和代理對象實現(xiàn)同一個業(yè)務(wù)接口。代理對象中的核心功能是由目標(biāo)對象來完成,代理對象負(fù)責(zé)增強功能;
② 目標(biāo)對象(被代理對象)必須實現(xiàn)接口;
③ 代理對象在程序運行前就已經(jīng)存在——>擴展代理類Agent;
④ 支持目標(biāo)對象靈活的切換,無法對功能靈活的處理——>動態(tài)代理可解決此問題。
<font color= 'red'>注意:靜態(tài)代理只適合業(yè)務(wù)功能固定不變的情況。(業(yè)務(wù)接口方法不進(jìn)行增加和減少,實現(xiàn)類就不需要改動)</font>
使用示例
需求描述:一個司機具有開車的能力,在開車前需要加油,開車結(jié)束后需要洗車和保養(yǎng),開車前后的動作,在不改變開車內(nèi)部邏輯的情況下,就可以采用靜態(tài)代理實現(xiàn),如下:
// 定義一個駕駛員接口,具有開車功能
public interface Driver {
/**
* 駕駛功能
*/
void drive();
}
// 小轎車駕駛員實現(xiàn)駕駛員接口
public class CarDriver implements Driver{
@Override
public void drive(){
System.out.println("正在開小轎車。");
}
}
// 小轎車代理類
public class CarAgent implements Driver{
@Override
public void drive(){
System.out.println("開車前要記得加油。");
CarDriver carDriver = new CarDriver();
carDriver.drive();
System.out.println("開車后記得洗車和保養(yǎng)");
}
}
// 測試類
public class CarAgentTest {
@Test
void testDrive(){
CarAgent carAgent = new CarAgent();
carAgent.drive();
}
}
[圖片上傳失敗...(image-44f9d6-1684200501221)]
動態(tài)代理
1、是使用反射和字節(jié)碼的技術(shù),在運行期創(chuàng)建指定接口或類的子類,以及其實例對象的技術(shù),通過這個技術(shù)可以無侵入的為代碼進(jìn)行增強,動態(tài)代理主要有JDK和CGLIB兩種方式;
2、如果代理方法很多,我們需要在每個代理方法都要寫一遍,很麻煩。而動態(tài)代理則不需要。
JDK
// 編寫動態(tài)代理類
public class CarJdkAgent<T> implements InvocationHandler {
private T target;
public CarJdkAgent(T target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable{
System.out.println("調(diào)用方法前");
Object ret = method.invoke(target,args);
return ret;
}
}
// 動態(tài)代理測試
public class DynAgentTest {
@Test
void testJdkAgent1(){
Driver carDriver = new CarDriver();
Driver proxyCarDriver = (Driver)Proxy.newProxyInstance(carDriver.getClass().getClassLoader(),
carDriver.getClass().getInterfaces(),new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//args表示有幾個參數(shù),這里只有一個參數(shù)
System.out.println("動態(tài)代理-開車前記得加油");
//這里調(diào)用真實對象的方法
return method.invoke(carDriver, args);
}
});
proxyCarDriver.drive();
}
@Test
void testJdkAgent2(){
Driver carDriver = new CarDriver();
InvocationHandler carJdkAgent = new CarJdkAgent<Driver>(carDriver);
Driver proxyDriver= (Driver)Proxy.newProxyInstance(carDriver.getClass().getClassLoader(),
carDriver.getClass().getInterfaces(), carJdkAgent);
proxyDriver.drive();
}
}
以上測試類兩種方式,方式一采用匿名方法調(diào)用,可以不編寫動態(tài)代理類,方式二通過編寫動態(tài)代理類實現(xiàn)。
CGLIB
1、動態(tài)代理需要被代理類實現(xiàn)接口,如果被代理類沒有實現(xiàn)接口,那么這么實現(xiàn)動態(tài)代理?這時候就需要用到CGLib了。這種代理方式就叫做CGlib代理;
2、Cglib代理也叫作子類代理,他是通過在內(nèi)存中構(gòu)建一個子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,然后加入自己需要的操作。因為使用繼承的方式,所以不能代理final 類;
3、需要導(dǎo)入cglib的jar包,另外Spring的核心包中已經(jīng)包括了Cglib功能,也可以導(dǎo)入spring-core-3.2.5.jar。
public class CarCglibAgent implements MethodInterceptor {
public Object getProxy(Class cls) {
// CGLIB <u>enhancer</u>增強類對象
Enhancer enhancer = new Enhancer();
// 設(shè)置增強類型
enhancer.setSuperclass(cls);
// 定義代理邏輯對象為當(dāng)前對象,要求當(dāng)前對象實現(xiàn)MethodInterceptor方法
enhancer.setCallback(this);
// 生成并返回代理對象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("開車前先加油");
// 執(zhí)行目標(biāo)對象的方法
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("開完車記得洗車");
return result;
}
// 測試調(diào)用
public static void main(String[] args) {
CarCglibAgent carCglibAgent = new CarCglibAgent();
CarDriver carDriver = (CarDriver)carCglibAgent.getProxy(CarDriver.class);
carDriver.drive();
}
Spring Aop名詞解釋
關(guān)于AOP的概念這里不做過多介紹,我們只需要知道其中有三個非常重要的概念:
aspect(切面)、pointcut(切入點)、advice(通知)。
pointcut(切入點)
也就是具體攔截的某個業(yè)務(wù)點。
分為 execution(路徑表達(dá)式)和 annotation 方式,被這兩種方式修飾的代碼將會被切面攔截處理;
advice(通知)
切面當(dāng)中的處理方式,聲明通知方法在業(yè)務(wù)層的執(zhí)行位置,類型如下:
@Before:前置通知,在方法執(zhí)行之前執(zhí)行
@After:后置通知,在方法執(zhí)行之后執(zhí)行
@AfterRunning:返回通知,在方法返回結(jié)果后執(zhí)行
@AfterThrowing:異常通知,在方法執(zhí)行異常后執(zhí)行
@Around:環(huán)繞通知,可以替代上述通知方法,并且可以控制方法是否執(zhí)行以及何時執(zhí)行。
aspect(切面)
即 pointcut+ advice ,和攔截器(HandlerInterceptorAdapter)的作用并沒有太大的區(qū)別,只是兩者的作用域不同,相對而言,切面的作用域更靈活一些。
使用介紹
基于自定義注解實現(xiàn)方式
// 定義自定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface CustomAnnotation {
public String ceShiValue() default "";
}
// 定義切面類
@Component
@Aspect
public class CustomAnnotationAspect {
@Pointcut(value = "@annotation(com.zhc.javabase.aop.CustomAnnotation)")
public void pointCut(){}
@Before("pointCut()")
public void beforeHandle(JoinPoint joinPoint){
System.out.println("基于注解方法執(zhí)行之前調(diào)用");
}
@After("pointCut()")
public void afterHandle(JoinPoint joinPoint){
System.out.println("方法執(zhí)行之后調(diào)用");
}
}
// Controller類中添加注解
@RestController
@RequestMapping("com/zhc/javabase/aop")
public class AopUseController {
@GetMapping("/firstAop")
@CustomAnnotation
public String firstAop(){
System.out.println("哈哈,aop學(xué)習(xí)");
return "hello aop";
}
}
基于路徑表達(dá)式方式
// 定義切面類
@Component
@Aspect
public class CustomAnnotationAspect {
@Pointcut(value = "execution(public * com.zhc.javabase.aop..*.*(..)))")
public void executionPointCut(){}
@Before("executionPointCut()")
public void beforeHandle1(JoinPoint joinPoint){
System.out.println("基于非注解方式,方法執(zhí)行之前調(diào)用");
}
}
在對應(yīng)包路徑下的接口會被切面所攔截,執(zhí)行對應(yīng)切面邏輯。
execution 切點表達(dá)式
由于Spring切面粒度最小是達(dá)到方法級別,而execution表達(dá)式可以用于明確指定方法返回類型,類名,方法名和參數(shù)名等與方法相關(guān)的部件,并且在Spring中,大部分需要使用AOP的業(yè)務(wù)場景也只需要達(dá)到方法級別即可,因而execution表達(dá)式的使用是最為廣泛的。如下是execution表達(dá)式的語法:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
這里問號表示當(dāng)前項可以有也可以沒有,其中各項的語義如下:
modifiers-pattern:方法的可見性,如public,protected;
ret-type-pattern:方法的返回值類型,如int,void等;
declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;
name-pattern:方法名類型,如buisinessService();
param-pattern:方法的參數(shù)類型,如java.lang.String;
throws-pattern:方法拋出的異常類型,如java.lang.Exception。