Springboot,spring進階第三天之AOP

 2023-11-30 阅读 40 评论 0

摘要:spring第三天之aop 在了解aop之前有必要先知道兩個知識 靜態代理和動態代理 Springboot, 靜態代理 某個對象提供一個代理,代理角色固定,以控制對這個對象的訪問。 代理類和委托類有共同的父類或父接口,這樣在任何使用委托類對象的地方都可以用代理對象替

spring第三天之aop

在了解aop之前有必要先知道兩個知識

靜態代理和動態代理

Springboot,

靜態代理

某個對象提供一個代理,代理角色固定,以控制對這個對象的訪問。

代理類和委托類有共同的父類或父接口,這樣在任何使用委托類對象的地方都可以用代理對象替代。

spring ioc是什么。代理類負責請求的預處理、過濾、將請求分派給委托類處理、以及委托類執行完請求后的后續處理。

代碼實現

spring ioc好處?接口

/*** 原對象和代理對象的共同行為*/
public interface Marry {// 結婚void toMarry();
}

spring aop概念,被代理對象

/*** 要結婚的你*/
public class you implements Marry {@Overridepublic void toMarry() {System.out.println("我要結婚了");}
}

spring的aop思想。代理對象

/***代理對象* 用來增強原對象的功能*/
public class MarryCompanyProxy implements Marry {//多態的方式private Marry marry;//提供一個有參構造 創建該對象的時候必須傳入一個Marry的實現類public MarryCompanyProxy(Marry marry) {this.marry = marry;}@Overridepublic void toMarry() {before();//原來的你marry.toMarry();after();}private void after() {System.out.println("結婚前的布置");}private void before() {System.out.println("結婚后的收場");}
}

測試類

/*** 實現靜態代理*/
public class StaticProxy {public static void main(String[] args) {//目標對象you you = new you();//代理對象MarryCompanyProxy marryCompanyProxy = new MarryCompanyProxy(you);//通過代理對象調目標對象中的方法marryCompanyProxy.toMarry();}
}

運行結果

通過代理我們可以在不改變被代理對象代碼的基礎上添加新功能

但是有一個問題,我要是有很多個需要被代理的對象呢?我們要分別創建對應的代理對象

這樣子太不靈活了

靜態代理的特點

1.目標角色固定

2在應用程序之前就得知目標角色

3.代理對象會增強目標對象的行為

4.有可能存在多個代理,產生類爆炸"(缺點)

動態代理

相比于靜態代理,動態代理在創建代理對象上更加的靈活,動態代理類的字節碼在程序運行時,

由lava反射機制動態產生。它會根據需要,通過反射機制在程序運行期,動態的為目標對象創建代理對象,

無需程序員手動編寫它的源代碼。動態代理不僅簡化了編程工作,而且提高了軟件系統的可擴展性,

因為反射機制可以生成任意類型的動態代理類。代理的行為可以代理多個方法

即滿足生產需要的同時又達到代碼通用的目的

動態代理呢又分為兩種

一種是JDK動態代理和cglib代理,我們來分別演示

JDK動態代理

/*** 原對象和代理對象的共同行為*/
public interface Marry {// 結婚void toMarry();
}/*** 要結婚的你*/
public class you implements Marry {@Overridepublic void toMarry() {System.out.println("我要結婚了");}
}/*** jdk動態代理對象* 代理的對象不是固定的* 傳過來任意一個目標對象即可* 每一個代理對象都需要實現InvocationHandler接口*/
public class JdkHandler implements InvocationHandler {//目標對象private Object target;//目標對象的類型不固定,創建時動態生成//通過有參構造傳遞目標對象public JdkHandler(Object target) {this.target = target;}/*** 1:這個方法是調用目標對象的方法(返回Object)* 2:增強目標對象的方法* @param proxy 調用該方法的代理實例* @param method 目標對象的方法* @param args 目標對象方法所需要的參數* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//增強前System.out.println("增強前");//調用目標對象的方法Object object = method.invoke(target, args);//增強后System.out.println("增強后");return object;}/*** 獲取代理對象* newProxyInstance這個方法是Jdk提供的,需要獲取三個參數* 第一個: Loader 獲取類加載器* 第二個:目標對象的接口數組* 第三個: InvocationHandler這個接口,我們實現了它,所以直接傳this即可*/public Object getProxy(){Object object = Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);return object;}}/*** @author 王磊* @date 2022/4/29* 測試類*/
public class JdkHandlerText {public static void main(String[] args) {//目標對象you you = new you();//目標對象的代理類JdkHandler jdkHandler = new JdkHandler(you);//得到代理對象Marry marry = (Marry) jdkHandler.getProxy();marry.toMarry();}
}

運行結果

?

通過觀察我們發現這種動態代理還是非常靈活的,

不管什么類型的對象都能被代理

但是但是!

JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能使用JDK的動態代理,

cglib是針對類來實現代理的,它的原理是對指定的目標類生成一個子類,并覆蓋其中方法實現增強,

但因為采用的是繼承,所以不能對final修飾的類進行代理。

cglib代理

下面來介紹cglib代理

首先要導入依賴

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version>
</dependency>

/*** 原對象和代理對象的共同行為*/
public interface Marry {// 結婚void toMarry();
}/*** 要結婚的你*/
public class you implements Marry {@Overridepublic void toMarry() {System.out.println("我要結婚了");}
}/*** Cglib動態代理* 實現原理:繼承思想*/
public class CglibInterceptor implements MethodInterceptor {//目標對象private Object target;//通過構造器傳入代理對象public CglibInterceptor(Object target) {this.target = target;}/*** 獲取代理對象*/public Object getProxy(){//通過 Enhancer對象中的create()方法生成一個類,用于生成代理對象Enhancer enhancer = new Enhancer();//設置父類(將目標類作為代理類的父類)enhancer.setSuperclass(target.getClass());//設置攔截器,回調對象為本身對象(參數是這個Callback 我們實現的這個類繼承了Callback,所以傳this)enhancer.setCallback(this);return enhancer.create();}/***攔截器* 目標對象的方法調用* 行為增強* @param o cglib動態生成的代理類的實例* @param method 實體類的方法 都被代理類的方法引用* @param objects   參數列表* @param methodProxy 生成的代理類對方法的代理引用(代理對象)* @return* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("方法的增強行為");//調用目標類的方法Object obj = method.invoke(target, objects);System.out.println("方法的增強行為");return obj;}
}//測試
public class CglibInterceptortest {public static void main(String[] args) {you you = new you();//得到攔截器CglibInterceptor cglibInterceptor = new CglibInterceptor(you);//得到代理對象Marry marry = (Marry) cglibInterceptor.getProxy();marry.toMarry();}
}

cglib代理不依賴于接口 ,不依賴與共同的行為,所以即使傳過來一個任意類都是可以實現代理的

兩種動態代理的區別

兩種動態代理的區別:

Jdk動態代理實現接口,cglib動態代理是繼承思想,繼承然后重寫

JDk動態代理(目標對象存在接口時)執行效率高于cglib

如果目標對象有接口實現,選擇JDK代理,如果沒有,選擇cglib代理

了解了這兩種代理,我們來學習springAop其實是非常簡單的

Aop的底層就是JDk動態代理+cglib

SpringAop

什么是AOP?

Aspect Oriented Programing面向切面編程,相比較oop面向對象編程來說,Aop關注的不再是程序代碼中某個類,某些方法,而aop考慮的更多的是一種面到面的切入,即層與層之間的一種切入,所以稱之為切面。聯想大家吃的漢堡(中間夾肉)。那么aop是怎么做到攔截整個面的功能呢?考慮前面學到的servletfilter/*的配置,實際上也是aop的實現

AOP能做什么?

AOP主要應用于日志記錄,性能統計,安全控制,事務處理等方面,實現公共功能性的重復使用。

AOP的特點

1.降低模塊與模塊之間的耦合度,提高業務代碼的聚合度。(高內聚低耦合)

2.提高了代碼的復用性。

3.提高系統的擴展性。(高版本兼容低版本)

4.可以在不影響原有的功能基礎上添加新的功能

AOP的底層實現

動態代理(IDK+CGLIB)

AOP基本概念

1.Joinpoint(連接點)

被攔截到的每個點,spring中指被攔截到的每一個方法,springaop一個連接點即代表一個方法的執行。

2.Pointcut(切入點)

對連接點進行攔截的定義(匹配規則定義規定攔截哪些方法,對哪些方法進行處理),spring有專門的表達式語言定義。

3.Advice(通知)

攔截到每一個連接點即(每一個方法)后所要做的操作

1.前置通知(前置增強)-before()執行方法前通知

2.返回通知(返回增強)一afterReturn方法正常結束返回后的通知

3.異常拋出通知(異常拋出增強)-afetrThrow()

4.最終通知一after無論方法是否發生異常,均會執行該通知。

5.環繞通知-around包圍一個連接點(joinpoint)的通知,如方法調用。這是最強大的一種通知類型。環繞

通知可以在方法調用前后完成自定義的行為。它也會選擇是否繼續執行連接點或直接返回它們自己的返回值或拋出異常來結束執行。

4.Aspect(切面)

切入點與通知的結合,決定了切面的定義,切入點定義了要攔截哪些類的哪些方法,通知則定義了攔截過方法后要做什么,切面則是橫切關注點的抽象,與類相似,類是對物體特征的抽象,切面則是橫切關注點抽象。

5.Target(目標對象)

被代理的目標對象

6.Weave(織入)

將切面應用到目標對象并生成代理對象的這個過程即為織入

7.Introduction(引入)

在不修改原有應用程序代碼的情況下,在程序運行期為類動態添加方法或者字段的過程稱為引入

xml文件實現aop

接下來是代碼實現

先搭建環境 導入坐標

<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.10.RELEASE</version>
</dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version>
</dependency>

然后是xml文件配置

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--包掃描--><context:component-scan base-package="com.xxx"/>

三層架構

?

/*** @author 王磊* @date 2022/4/29*/
@Service
public class UserServiceImpl {public void save(){System.out.println("userservice...");}
}

然后要定義一個切面類

/*** 切面* 切入點和通知的抽象* 定義切入點和通知* 切入點:定義要攔截哪些類的哪些方法* 通知:定義了攔截方法之后要做什么*/
@Component//將對象交給ioc進行實例化
public class LogCut02 {/*** 切入點* 定義要攔截哪些類的哪些方法* 匹配規則,攔截申明方法** @Pointcut("匹配規則) 定義切入點* aop一般攔截的都是業務層service* <p>* Aop切入點表達式* 1:執行所有公共方法* ("execution(public *(..)")* <p>* 2:執行任意的set方法* ("execution(* set *(..))* <p>* 3:設置指定包下的任意類的任意方法* ("execution(* com.itcast.service..*.*(..)")* <p>* 4:設置指定包及子包下的的任意類的任意方法* 表達式的第一個** 代表的時方法的修飾范圍 (public private protected)* 如果取值是*號,表示所有范圍*/public void cut() {}/*** 聲明前置通知,并將通知應用到指定的切入點上* 目標類的方法執行前通知*/public void before() {System.out.println("前置通知");}/*** 聲明返回通知,并將通知應用到指定的切入點上* 目標類的方法在無異常執行后執行該通知*/public void afterReturn() {System.out.println("返回通知");}/*** 聲明最終通知,并將通知應用到指定的切入點上* 目標類的方法在執行后執行該通知(不管有沒有異常都會執行)*/public void after() {System.out.println("最終通知");}/*** 聲明異常通知,并將通知應用到指定的切入點上* 目標類的方法在執行后有異常時執行該通知*/public void afterThrow(Exception e) {System.out.println("異常通知"+e.getMessage());//異常原因}/*** 聲明環繞通知,并將通知應用到指定的切入點上* 目標類的方法執行前后都可以通過環繞通知定義相應的處理*  需要通過顯示調用的方法,否則無法訪問指定方法 pjp.proceed(); 手動調用* @param pjp* @return*/public Object around(ProceedingJoinPoint pjp) {System.out.println("環繞通知-前置通知");try {//顯示調用對應的方法Object proceed = pjp.proceed();System.out.println("環繞通知中的返回通知");} catch (Throwable e) {e.printStackTrace();System.out.println("環繞通知的異常通知");}System.out.println("環繞通知-最終通知");return new Object();}
}

然后我們在xml文件中進行配置切面

<aop:config><!--aop切面--><aop:aspect ref="logCut02"><!--切入點--><aop:pointcut id="cut" expression="execution(* com.ithcast.Service..*.*(..))"/><!--配置前置通知,設置前置通知對應的方法名以及切入點--><aop:before method="before"  pointcut-ref="cut"/><!--配置返回通知,設置前置通知對應的方法名以及切入點--><aop:after-returning method="afterReturn" pointcut-ref="cut"/><!--配置最終通知,設置前置通知對應的方法名以及切入點--><aop:after method="after" pointcut-ref="cut"/><!--配置返回通知,設置前置通知對應的方法名以及切入點--><aop:after-throwing method="afterThrow" pointcut-ref="cut" throwing="e"/><!--配置環繞通知,設置前置通知對應的方法名以及切入點--><aop:around method="around" pointcut-ref="cut"/></aop:aspect>
</aop:config>

編寫測試類

/*** @author 王磊* @date 2022/4/29*/
public class app {public static void main(String[] args) {//獲取上下文環境ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");//通過類型獲取bean// UserServiceImpl service = (UserServiceImpl) context.getBean(UserServiceImpl.class);//通過名稱獲取,這個名字是默認第一位字母小寫UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("userServiceImpl");serviceImpl.save();}
}

執行結果:

?

注解實現AOP

我們重新創建一個切面類

之前xml文件中配置的切面通通可以刪除掉

上代碼

/*** 切面* 切入點和通知的抽象* 定義切入點和通知* 切入點:定義要攔截哪些類的哪些方法* 通知:定義了攔截方法之后要做什么*/
@Component//將對象交給ioc進行實例化
@Aspect//聲明當前是一個切面
public class LogCut {/*** 切入點* 定義要攔截哪些類的哪些方法* 匹配規則,攔截申明方法** @Pointcut("匹配規則) 定義切入點* aop一般攔截的都是業務層service* <p>* Aop切入點表達式* 1:執行所有公共方法* ("execution(public *(..)")* <p>* 2:執行任意的set方法* ("execution(* set *(..))* <p>* 3:設置指定包下的任意類的任意方法* ("execution(* com.itcast.service..*.*(..)")* <p>* 4:設置指定包及子包下的的任意類的任意方法* 表達式的第一個** 代表的時方法的修飾范圍 (public private protected)* 如果取值是*號,表示所有范圍*/@Pointcut("execution(* com.ithcast.Service..*.*(..))")public void cut() {}/*** 聲明前置通知,并將通知應用到指定的切入點上* 目標類的方法執行前通知*/@Before("cut()")public void before() {System.out.println("前置通知");}/*** 聲明返回通知,并將通知應用到指定的切入點上* 目標類的方法在無異常執行后執行該通知*/@AfterReturning("cut()")public void afterReturn() {System.out.println("返回通知");}/*** 聲明最終通知,并將通知應用到指定的切入點上* 目標類的方法在執行后執行該通知(不管有沒有異常都會執行)*/@After("cut()")public void after() {System.out.println("最終通知");}/*** 聲明異常通知,并將通知應用到指定的切入點上* 目標類的方法在執行后有異常時執行該通知*/@AfterThrowing(value = "cut()",throwing = "e")public void afterThrow(Exception e) {System.out.println("異常通知"+e.getMessage());//異常原因}/*** 聲明環繞通知,并將通知應用到指定的切入點上* 目標類的方法執行前后都可以通過環繞通知定義相應的處理*  需要通過顯示調用的方法,否則無法訪問指定方法 pjp.proceed(); 手動調用* @param pjp* @return*/@Around("cut()")public Object around(ProceedingJoinPoint pjp) {System.out.println("環繞通知-前置通知");try {//顯示調用對應的方法Object proceed = pjp.proceed();System.out.println(pjp.getTarget());System.out.println("環繞通知中的返回通知");} catch (Throwable e) {e.printStackTrace();System.out.println("環繞通知的異常通知");}System.out.println("環繞通知-最終通知");return new Object();}
}

運行結果

注解開發還是非常方便的,也是眼下企業的主流

SpringAOP總結

代理模式實現三要素

1.接口定義

2.目標對象與代理對象必須實現統一接口

3.代理對象持有目標對象的引用增強目標對象行為

代理模式實現分類以及對應區別

1.靜態代理:手動為目標對象制作代理對象,即在程序編譯階段完成代理對象的創建

2.動態代理:在程序運行期動態創建目標對象對應代理對象。

3.idk動態代理:被代理目標對象必須實現某一或某一組接口實現方式通過回調創建代理對象。

4.cglib動態代理:被代理目標對象可以不必實現接口,繼承的方式實現。

動態代理相比較靜態代理,提高開發效率,可以批量化創建代理,提高代碼復用率。

Aop 理解

1.面向切面,相比oop關注的是代碼中的層或面

2.解耦,提高系統擴展性3提高代碼復用

Aop 關鍵詞

1.連接點每一個方法

2.切入點:匹配的方法集合

3.切面:連接點與切入點的集合決定了切面,橫切關注點的抽象

4.通知:幾種通知

5.目標對象被代理對象

6.織入:程序運行期將切面應用到目標對象并生成代理對象的過程

7.引入:在不修改原始代碼情況下,在程序運行期為程序動態引入方法或字段的過程

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

原文链接:https://808629.com/188205.html

发表评论:

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

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息