中臺前端框架,Spring框架(中) AOP

 2023-10-15 阅读 34 评论 0

摘要:Spring(中) AOP (一)代理模式 1.靜態代理 靜態代理在使用時,需要定義接口或者父類,被代理對象(即目標對象)與代理對象一起實現相同的接口或者是繼承相同父類。 舉例: package com.deserts.proxy.staticproxy;/*** @ClassName ProxyTes

Spring(中) AOP

(一)代理模式

1.靜態代理

靜態代理在使用時,需要定義接口或者父類,被代理對象(即目標對象)與代理對象一起實現相同的接口或者是繼承相同父類。

舉例:

package com.deserts.proxy.staticproxy;/*** @ClassName ProxyTest* @Description TODO* @Author deserts* @Date 2020/10/16 12:12*/interface IShoeFactory{void produce();
}class ShoeFactory implements IShoeFactory{@Overridepublic void produce() {System.out.println("生產鞋子");}
}class ShoeProxy implements IShoeFactory{private IShoeFactory shoeFactory;public ShoeProxy (IShoeFactory shoeFactory){this.shoeFactory = shoeFactory;}@Overridepublic void produce() {shoeFactory.produce();}
}public class ProxyTest {public static void main(String[] args) {ShoeProxy proxy = new ShoeProxy(new ShoeFactory());proxy.produce();}
}

優點:在不修改目標對象的功能前提下, 能通過代理對象對目標功能擴展

缺點:因為代理對象需要與目標對象實現一樣的接口,所以會有很多代理類;或者需要實現很多接口,代碼混亂。

中臺前端框架,一旦接口增加方法,目標對象與代理對象都要維護。

2.JDK動態代理

基本介紹:

  1. 代理對象,不需要實現接口,但是目標對象要實現接口,否則不能用動態代理

  2. 代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象

  3. 動態代理也叫做:JDK代理、接口代理

Spring aop,舉例:

package com.deserts.proxy.jdkproxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @ClassName JDKProxyTest* @Description TODO* @Author deserts* @Date 2020/10/16 12:23*/interface IShoeFactory{void produce();
}
class ShoeFactory implements IShoeFactory{@Overridepublic void produce() {System.out.println("生產鞋子中");}
}class ShoeProxyFactory{private Object target;public ShoeProxyFactory(Object target) {this.target = target;}public Object getProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object returnResult = method.invoke(target, args);return returnResult;}});}
}public class JDKProxyTest {public static void main(String[] args) {IShoeFactory proxy = (IShoeFactory)new ShoeProxyFactory(new ShoeFactory()).getProxyInstance();proxy.produce();}}

(二)AOP概述

1.一個動態代理的例子

1.1 要求

①執行加減乘除運算

②日志:在程序執行期間追蹤正在發生的活動

1.2 實現

創建計算器接口,讓計算器類去實現;創建動態代理的類,利用構造器或者方法傳入計算器對象,創建獲取被代理類的方法。

1.ICalculator接口

public interface ICalculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}

Java前端框架、2.Calculator類

package com.deserts.aop.proxy;/*** @ClassName Calculator* @Description TODO* @Author deserts* @Date 2020/10/15 19:23*/
public class Calculator implements ICalculator{@Overridepublic int add(int i, int j) {int result = i + j;return result;}@Overridepublic int sub(int i, int j) {int result = i - j;return result;}@Overridepublic int mul(int i, int j) {int result = i * j;return result;}@Overridepublic int div(int i, int j) {int result = i / j;return result;}
}

3.代理類

package com.deserts.aop.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.function.Predicate;/*** @ClassName ProxyFactory* @Description TODO* @Author deserts* @Date 2020/10/15 19:26*/
public class ProxyFactory {Object target;public ProxyFactory(Object target){this.target = target;}public Object getProxy(){return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object returnResult = null;try {Log.before(method.getName(), args);returnResult = method.invoke(target, args);Log.after(method.getName(), returnResult);} catch (Exception e) {Log.exceptionInfo(method.getName(), e);} finally {Log.end();}return returnResult;}});}
}

4.封裝日志功能,錯誤信息的類

package com.deserts.aop.proxy;import java.util.Arrays;/*** @ClassName Log* @Description TODO* @Author deserts* @Date 2020/10/15 19:54*/
public class Log {public static void before(String methodName, Object... args){System.out.println(methodName + "即將執行,參數為:" + Arrays.asList(args));}public static void after(String methodName, Object result){System.out.println(methodName + "執行結束,結果為:" + result);}public static void exceptionInfo(String methodName, Exception e){System.out.println(methodName + "執行過程出現了異常,異常信息為:" + e.getCause());}public static void end(){System.out.println("程序執行結束");}
}

5.測試類

package com.deserts.aop.proxy;/*** @ClassName Client* @Description TODO* @Author deserts* @Date 2020/10/15 19:34*/
public class Client {public static void main(String[] args) {ICalculator proxy = (ICalculator)new ProxyFactory(new Calculator()).getProxy();proxy.add(1,2);proxy.div(2, 0);}
}

2.AOP概述

  1. AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,是對傳統 OOP(Object-Oriented Programming,面向對象編程)的補充。

    • 面向對象 縱向繼承機制
    • ? 面向切面 橫向抽取機制
  2. VB中的框架?AOP編程操作的主要對象是切面(aspect),而切面用于模塊化橫切關注點(公共功能)

  3. 在應用AOP編程時,仍然需要定義公共功能,但可以明確的定義這個功能應用在哪里,以什么方式應用,并且不必修改受影響的類。這樣一來橫切關注點就被模塊化到特殊的類里——這樣的類我們通常稱之為“切面”

  4. AOP的好處:

    ① 每個事物邏輯位于一個位置,代碼不分散,便于維護和升級

    ② 業務模塊更簡潔,只包含核心業務代碼

    Spring框架是什么?③ AOP圖解

    image-20201016195440238

3.AOP術語

3.1 橫切關注點

從每個方法中抽取出來的同一類非核心業務。

image-20201016221206861

3.2 切面(Aspect)

封裝橫切關注點信息的類,每個關注點體現為一個通知方法。

3.3 通知(Advice)

中框架和邊框架。切面必須要完成的各個具體工作

3.4 目標(Target)

被通知的對象

3.5 代理(Proxy)

向目標對象應用通知之后創建的代理對象

3.6 連接點(Joinpoint)

橫切關注點在程序代碼中的具體體現,對應程序執行的某個特定位置。例如:類某個方法調用前、調用后、方法捕獲到異常后等。

3.7 切入點(pointcut)

定位連接點的方式。每個類的方法中都包含多個連接點,所以連接點是類中客觀存在的事物。如果把連接點看作數據庫中的記錄,那么切入點就是查詢條件——AOP可以通過切入點定位到特定的連接點。切點通過org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作為連接點的查詢條件。

3.8 圖解

Spring Framework、image-20201016234318008

4.AspectJ

4.1 簡介

AspectJ:Java社區里最完整最流行的AOP框架。

在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

4.2 在Spring中啟用AspectJ注解支持

1.導入jar包

  • com.springsource.net.sf.cglib-2.2.0.jar
  • com.springsource.org.aopalliance-1.0.0.jar
  • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  • spring-aop-4.0.0.RELEASE.jar
  • spring-aspects-4.0.0.RELEASE.jar

2.引入AOP命名空間

Springboot?image-20201016201929600

3.配置掃描包,配置被代理的類和日志類

日志類需要@Aspect注解,@Before注解將方法配置為前置通知,需要指定value值,值為切入點表達式。同時需要@Component注解讓Spring組件掃描去管理。

image-20201016223659959

被代理類也需要@Component注解,讓Spring去管理:

spring開發框架、image-20201016223824669

測試類使用時需要創建Spring容器去管理被配置好的類的對象:

image-20201016223927184

運行結果:

image-20201016230603777

4.3 用AspectJ注解聲明切面

Spring bean、① @Before:前置通知,在方法執行之前執行

② @After:后置通知,在方法執行之后執行

③ @AfterRunning:返回通知,在方法返回結果之后執行

④ @AfterThrowing:異常通知,在方法拋出異常之后執行

⑥ @Around:環繞通知,圍繞著方法執行

Spring框架很難么?image-20201016235309381

(三) AOP細節

1.細節一:容器創建的對象

IOC容器中保存的是組件的代理對象,可以理解成這個代理對象也去實現了目標對象所實現的接口

如:

image-20201017084855099

運行結果:

vb中框架是哪個,image-20201017084927396

可以看到,該對象是class com.sun.proxy.$Proxy7代理對象,底層原理是jdk動態代理。

除了實現接口的方式去代理,spring還提供了cglib代理,為沒有接口的類進行代理。

如:

image-20201017085328626

網頁設計中框架是什么、測試類:

image-20201017085532677

運行結果:

image-20201017085553846

可以看到cglib為我們創建了代理對象

2.細節二:切入點表達式

2.1 *:

1)匹配一個或多個字符,如:execution(public int com.deserts.aop.proxy.Calculator.*(int,int))

  1. 匹配任意類型的參數,如:execution(public int com.deserts.aop.proxy.Calculator.add(*,*))

  2. 只能匹配一層路徑,如:execution(public int com.deserts.aop.*.Calculator.add(int,int))

4)權限類型前不能寫,可以不寫權限類型。public(可選類型)。

2.2 … :
  1. 匹配任意多個參數,如:execution(public int com.deserts.aop.proxy.Calculator.add(…))

2)匹配多層路徑,如:execution(public int com.deserts…Calculator.*(int,int))

2.3 兩種類型

最精確的:execution(public int com.deserts.aop.proxy.Calculator.add(int,int))

最模糊的:execution(*.*(…)) 不建議寫,見方法就切

2.4 使用&&、||、!

在AspectJ中,切入點表達式可以通過 “&&”、“||”、“!”等操作符結合起來。

表達式含義
execution (* *.add(int,…)) || excution( *.sub(int, …))
!execution (* *.add(int,…))匹配不是任意類中第一個參數為int類型的add方法

3.細節三:通知方法的執行順序

正常執行:@Before(前置通知) ==== @After(后置通知) ==== @AfterReturning(正常返回)

異常執行: @Before(前置通知) ==== @After(后置通知) ==== @AfterThrowing(方法異常)

4.細節四:JoinPoint獲取獲取目標方法的信息

主要通過傳入通知方法的JoinPoint joinPoint參數,在方法內調用參數相關方法進行獲取,如:

image-20201017094359568

5.細節五:使用returning、throwing來接收返回值、異常

接收返回值:

image-20201017094838293

接收異常信息:

image-20201017094901013

6.細節六:Spring對通知方法的約束

1)spring對通知方法的沒有太大約束,唯一的約束是參數列表的參數不能亂寫,如果有未知的參數,需要告訴spring這個參數是用來做什么的,因為這些通知方法是spring所管理的,主要是通過反射來調用的。

2)如returning、throwing指定接收返回值、異常的參數,參數類型要盡量的大,如Object、Exception

7.細節七:抽取可重用的切入點表達式

  • 抽取可重用的切入點表達式:

    • 1.創建一個返回值為void,參數為空的空方法
    • 2.給方法加上注解@PointCut,并在參數里寫入要抽取的切入點表達式
    • 3.將方法名放入要使用該表達式的值里面
  • 示例:

    image-20201017101904396

8.細節八:環繞通知

  • 環繞通知是通知里面最強大的,可以當作其它四個通知的整合。
  • 對于環繞通知來說,連接點的參數類型必須是ProceedingJoinPoint。它是 JoinPoint的子接口,允許控制何時執行,是否執行連接點。
  • 在環繞通知中需要明確調用ProceedingJoinPoint的proceed()方法來執行被代理的方法。如果忘記這樣做就會導致通知被執行了,但目標方法沒有被執行
  • 注意:環繞通知的方法需要返回目標方法執行之后的結果,即調用 joinPoint.proceed();的返回值,否則會出現空指針異常。

示例:

image-20201017104615137

9.細節九:環繞通知的執行順序&拋出異常讓其它通知感受到

測試:將四個普通通知和環繞通知都打開,運行查看結果:

image-20201017105612649

通知順序:

image-20201017105643073

新的順序:

image-20201017105757256

讓普通通知感受到異常:將該異常拋出去

image-20201017110031814

10.細節十:多切面運行順序

都是普通通知時,會根據切面類的名稱的字母順序,小的在外層,如LogUtil類在VaAspect類的外層,先進去后出來

image-20201017111502124

若想改變切面運行順序,可以加上@Order注解,如:

image-20201017111904489

value值越小,優先級越高,也就是在最外層。

當有環繞通知時,環繞通知會先于該切面類的普通通知去執行,因為該環繞通知是加在這個切面類的。

如環繞通知加到LogUtils上:

image-20201017112629425

AOP使用場景

1.日志記錄

2.權限驗證

3.安全檢查

4.事務控制

(四)以xml方式配置切面

1.基于注解的AOP

image-20201017113217288

2.基于配置的AOP

步驟

  1. 在IOC容器中創建管理目標類的bean和切面類的bean

    image-20201017141619307

  2. 配置切面類中的通知方法,配置切面時需要引用上面配置好的切面類

    image-20201017141640049

  3. 測試

    image-20201017141747752

3.注解和配置的選擇

注解:快速方便

配置:功能完善

選擇:重要的用配置,不重要的用注解

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/137216.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息