本篇內(nèi)容介紹了“spring框架入門之怎么使用切面編程AOP”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
專注于為中小企業(yè)提供成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)交口免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上千企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
特點:字節(jié)碼隨用隨創(chuàng)建,隨用隨加載
作用:在不修改源碼的基礎(chǔ)上對方法進(jìn)行增強
分類:
基于接口的動態(tài)代理 | 基于子類的動態(tài)代理 | |
---|---|---|
涉及的類 | Proxy | Enhancer |
提供者 | JDK官方 | 第三方庫cglib |
如何創(chuàng)建代理對象 | 使用Proxy中的 newProxyInstance 方法 | 使用Enhancer類中的create 方法 |
創(chuàng)建代理對象的要求 | 被代理類至少實現(xiàn)一個接口,沒有則不能使用 | 被代理對象不能是最終類 |
newProxyInstance方法的參數(shù):
ClassLoader : 類加載器。用于加載代理對象字節(jié)碼,和被代理類使用相同的類加載器。
寫法: 代理對象.getClass().getClassLoader()
Class[] : 字節(jié)碼數(shù)組。用于讓代理對象和被代理對象有相同方法。
寫法: 代理對象.getClass().getInterfaces()
InvocationHandler:用于增強的代碼。書寫對被代理方法增強的代碼,一般書寫此接口的實現(xiàn)類,通常情況下是匿名內(nèi)部類,但不是必須的,此接口的實現(xiàn)類一般誰用到誰寫。
InvocationHandler參數(shù)中的invoke方法,執(zhí)行被代理對象的任何接口方法都會經(jīng)過該方法。方法參數(shù)及其含義:
proxy :代理對象的引用
method :當(dāng)前執(zhí)行的方法
args:當(dāng)前執(zhí)行方法所需的參數(shù)
返回值:與被代理類有相同的返回值
代碼示例:
public class Client { public static void main(String[] args) { final ProducerImpl producer = new ProducerImpl(); producer.saleProduct(1000f);// 銷售產(chǎn)品,拿到錢1000.0 System.out.println("對方法進(jìn)行增強后。。。。。"); Producer proxyProduct = (Producer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 執(zhí)行被代理對象的任何接口方法都會經(jīng)過該方法 * 方法的參數(shù)含義 * @param proxy 代理對象的引用 * @param method 當(dāng)前執(zhí)行方法 * @param args 當(dāng)前執(zhí)行方法所需的參數(shù) * @return 和被代理對象有相同的返回值 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 提供增強的代碼 Object returnValue = null; // 1.獲取方法的執(zhí)行參數(shù) Float money = (Float) args[0]; // 2.判斷當(dāng)前方法是不是銷售方法 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer, money * 0.8f); } return returnValue; } }); proxyProduct.saleProduct(1000f);// 銷售產(chǎn)品,拿到錢800.0 } }
create方法的參數(shù):
Class:字節(jié)碼。用于指定被代理對象的字節(jié)碼。
Callback:用于提供增強的代碼,類似于基于接口的動態(tài)代理的invoke
方法。一般寫的是該接口的子接口實現(xiàn)類 MethodInterceptor
create參數(shù)中 MethodInterceptor
的方法參數(shù):
o :代理對象的引用
method :當(dāng)前執(zhí)行的方法
objects:當(dāng)前執(zhí)行方法所需的參數(shù)
methodProxy:當(dāng)前執(zhí)行方法的代理對象
代碼示例:
public class Client { final Producer producer = new Producer(); public static void main(String[] args) { final Producer producer = new Producer(); producer.saleProduct(1000f);// 售賣商品,得到錢1000.0 System.out.println("對方法進(jìn)行增強后。。。。。"); Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 執(zhí)行任何被處理對象的任何方法都會經(jīng)過該方法 * @param o 代理對象的引用 * @param method 當(dāng)前的執(zhí)行方法 * @param objects 當(dāng)前執(zhí)行方法所需的參數(shù) * @param methodProxy 當(dāng)前執(zhí)行方法的代理對象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 提供增強的方法 Object returnValue = null; // 1.獲取當(dāng)前方法的執(zhí)行參數(shù) Float money = (Float) objects[0]; // 2.判斷當(dāng)前的方法是不是銷售動作 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer, money * 0.8f); } return returnValue; } }); cglibProducer.saleProduct(1000f);// 售賣商品,得到錢800.0 } }
動態(tài)代理的一般使用方式:
獲取被代理類對象(被代理對象的字節(jié)碼、被代理類對象的類加載器等信息)
在代理類提供的方法中對被代理類中的方法進(jìn)行增強
spring中的AOP是通過配置的方式實現(xiàn)動態(tài)代理
**Joinpoint(連接點):**指被攔截到的點。在spring中這些點指的是方法,因為spring只支持方法類型的連接點??梢岳斫鉃闃I(yè)務(wù)層中所有的方法。
**Pointcut(切入點):**指需要對那些Joinpoint進(jìn)行攔截的定義??梢岳斫鉃楸辉鰪姷姆椒?。
**Advice(通知/增強):**指攔截到Joinpoint后需要做的事情。通知類型:前置通知,后置通知,異常通知,最終通知,環(huán)繞通知。
前置通知:在執(zhí)行業(yè)務(wù)層方法前的通知;
后置通知:在執(zhí)行業(yè)務(wù)層方法后的通知;
異常通知:catch中的通知;
最終通知:在finally中的通知;
環(huán)繞通知:整個invoke方法執(zhí)行就是環(huán)繞通知;
**Introduction(引介):**一種特殊的通知在不修改類代碼的前提下,Introduction可以在運行期為類動態(tài)的添加一些方法或Field。
Target(目標(biāo)對象):代理的目標(biāo)對象。
**Weaving(織入):**指把增強應(yīng)用到目標(biāo)對象來創(chuàng)建代理對象的過程。spring是動態(tài)代理織入的,而AspectJ采用編譯期織入和類裝載期織入。
**Proxy(代理):**一個類被AOP織入增強后,就產(chǎn)生一個結(jié)果代理類。
**Aspect(切面):**是切入點和通知(引介)的結(jié)合。
開發(fā)階段:
編寫核心業(yè)務(wù)代碼(主線開發(fā),熟悉業(yè)務(wù)代碼即可進(jìn)行開發(fā))
把公共代碼提取出來,制作成通知。(開發(fā)最后階段)
在配置文件中聲明切入點與通知之間的關(guān)系,即切面。
運行階段:
spring框架監(jiān)控切入點的方法執(zhí)行。一旦監(jiān)控到切入點方法被運行,使用代理機制,動態(tài)創(chuàng)建目標(biāo)對象的代理對象,通知類別,在代理對象的對應(yīng)位置,將通知對應(yīng)的功能織入,完成完整的代碼邏輯運行。
spring中的AOP會根據(jù)目標(biāo)是否實現(xiàn)了接口來決定采用哪種動態(tài)代理的方式
3.1 將通知類交由IoC容器管理
將通知類注冊到spring的IoC容器中
<bean id="" class=""> <property name="" ref=""></property> </bean>
3.2 使用 <aop:config>
標(biāo)簽進(jìn)行AOP配置
用于聲明aop配置
<aop:config> <!-- 配置的代碼寫在此處 --> </aop:config>
3.3 使用 <aop:aspect>
配置切面
用于配置切面
屬性:
① id屬性:是給切面提供一個唯一標(biāo)識
② ref屬性:是指定通知類bean的Id。
<aop:aspect id="" ref=""> <!-- 在這里配置通知類型 --> </aop:aspect>
<aop:before>
:用于配置前置通知
<aop:after-return>
:用于配置后置通知
<aop:after-throwing>
:用于配置異常通知
<aop:after>
:用于配置最終通知
<aop: around>
:用于配置環(huán)繞通知
① method屬性:用于指定Logger類中哪個方法是前置通知
② pointcut屬性:用于指定切入點表達(dá)式,該表達(dá)式的含義指的是對業(yè)務(wù)層中哪些方法增強
③ pointcut-ref屬性:用于指定切入點表達(dá)式的id
3.4 使用 <aop:pointcut>
配置切入點表達(dá)式
用于配置切入點表達(dá)式,就是指定對那些類進(jìn)行的那些方法進(jìn)行增強
屬性:
① id屬性:用于指定切入點的唯一標(biāo)識
② expression屬性:用于配置切入點表達(dá)式
<aop:pointcut id="" expression="execution()"/>
代碼示例:
<!-- 配置service對象 --> <bean id="accountService" class="cn.bruce.service.impl.AccountServiceImpl"></bean> <bean id="testService" class="cn.bruce.service.impl.TestServiceImpl"></bean> <!-- 配置Logger類 --> <bean id="logger" class="cn.bruce.utils.Logger"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置通知類型,建立通知方法和切入點方法的關(guān)聯(lián) --> <aop:before method="printLog" pointcut="execution(* cn.bruce.service.impl.*.*(..))"></aop:before> <aop:after method="printLog" pointcut="execution(* cn..impl.Test*.*(cn.bruce.domain.Account))"></aop:after> </aop:aspect> </aop:config>
關(guān)鍵字:execution("表達(dá)式")
表達(dá)式寫法:訪問修飾符 返回值 包名.***.包名.類名.方法名(參數(shù)列表)
標(biāo)準(zhǔn)寫法:public void cn.bruce.service.impl.AccountServiceImpl.saveAccount()
訪問修飾符可以省略(訪問權(quán)限不能寫 *
),表示匹配任意類型的訪問權(quán)限,但Spring現(xiàn)在只支持public權(quán)限;
void cn.bruce.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值;
* cn.bruce.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包,有幾級包就要寫幾個 *
;
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用 ..
表示當(dāng)前包及其子包
* cn..AccountServiceImpl.saveAccount()
類名和方法名都可以使用通配符代替
* *..*.*()
**參數(shù)列表:**直接寫數(shù)據(jù)類型
基本數(shù)據(jù)類型直接寫名稱,如:int long double boolean
引用數(shù)據(jù)類型要寫全類名,如:cn.bruce.domain.Accout
可以使用通配符 *
表示任意類型,但是必須有參數(shù)
可以使用通配符 *
進(jìn)行占位,如:* *..*.*(*, int)
可以使用 ..
表示有無參數(shù)均可,有參數(shù)可以是任意類型 * *..*.*(..)
全通配寫法:* *..*.*(..)
開發(fā)中切入點表達(dá)式的通常寫法:如:切到業(yè)務(wù)層實現(xiàn)類下的所有方法 * cn.bruce.service.impl.*.*(..)
前置通知 <aop:before>
:在切入點方法執(zhí)行之前執(zhí)行
后置通知 <aop:after-returning>
:在切入點方法執(zhí)行之后執(zhí)行。后置通知和異常通知永遠(yuǎn)只能執(zhí)行一個
異常通知 <aop:after-throwing>
:在切入點方法執(zhí)行產(chǎn)生異常后執(zhí)行。異常通知和后置通知永遠(yuǎn)只能執(zhí)行一個
最終通知 <aop:after>
:無論切入點方法是否正常執(zhí)行,它都會在其后面執(zhí)行
環(huán)繞通知 <aop:around>
:是spring框架為我們提供的一種可以在代碼中手動控制增強方法何時執(zhí)行的方式。
代碼示例:
<!-- 配置AOP --> <aop:config> <!-- 配置切入點表達(dá)式,id屬性是表達(dá)式的唯一標(biāo)識,expression屬性用于指定表達(dá)式內(nèi)容 此標(biāo)簽可以寫在aop:aspect標(biāo)簽內(nèi)部只能當(dāng)前切面使用 寫在aop:aspect外面,此時表示所有的切面可用 --> <aop:pointcut id="loggerPointCut" expression="execution(* cn..impl.Account*.*(..))"/> <!-- 配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 前置通知:在切入點方法執(zhí)行之前執(zhí)行 --> <aop:before method="beforePrintLog" pointcut-ref="loggerPointCut"></aop:before> <!-- 后置通知:在切入點方法執(zhí)行之后執(zhí)行。后置通知和異常通知永遠(yuǎn)只能執(zhí)行一個 --> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="loggerPointCut"></aop:after-returning> <!-- 異常通知:在切入點方法執(zhí)行產(chǎn)生異常后執(zhí)行。異常通知和后置通知永遠(yuǎn)只能執(zhí)行一個 --> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="loggerPointCut"></aop:after-throwing> <!-- 最終通知:無論切入點方法是否正常執(zhí)行,它都會在其后面執(zhí)行 --> <aop:after method="afterPrintLog" pointcut-ref="loggerPointCut"></aop:after> <!-- 環(huán)繞通知:是spring框架為我們提供的一種可以在代碼中手動控制增強方法何時執(zhí)行的方式。 --> <aop:around method="aroundPrintLog" pointcut-ref="loggerPointCut"></aop:around> </aop:aspect> </aop:config>
配置步驟:
①導(dǎo)入maven坐標(biāo)
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> </dependencies>
② 書寫spring配置類,開啟包掃描和注解支持
@configuration @ComponentScan("cn.bruce") // 開啟包掃描,配置需要掃描的包 @EnableAspectJAutoProxy(proxyTargetClass = true) // 開啟注解驅(qū)動 public class SpringConfiguration { }
③ 將業(yè)務(wù)層實體類交由IoC容器管理
@Service("testService") public class TestServiceImpl implements TestService { @Override public void testOfVoid() { System.out.println("testOfVoid is running......"); } @Override public void testOfInt(int i) { System.out.println("testOfInt is running......number is" + i); } @Override public void testOfInteger(Integer i) { // i = 1/0; System.out.println("testOfInteger is running......number is" + i); } @Override public void testOfAccount(Account account) { int i = 1/0; System.out.println("testOfInt is running......number is" + account); } }
④ 書寫切面類,聲明為切面類并設(shè)置切入點和通知類型
@Component("logger") @Aspect // 表示此類為切面類 public class Logger { @Pointcut("execution(* cn..impl.*.*(..))") // 指定切入點表達(dá)式 private void pointcut(){} /** * 前置通知 */ @Before("execution(* cn..impl.*.*(int))") public void beforePrintLog(){ System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日志了。。。"); } /** * 后置通知 */ @AfterReturning("execution(* cn..impl.*.*(Integer))") public void afterReturningPrintLog(){ System.out.println("后置通知Logger類中的afterReturningPrintLog方法開始記錄日志了。。。"); } /** * 異常通知 */ @AfterThrowing("pointcut()") public void afterThrowingPrintLog(){ System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日志了。。。"); } /** * 最終通知 */ @After("execution(* cn..impl.*.*())") public void afterPrintLog(){ System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日志了。。。"); } /** * 環(huán)繞通知 */ @Around("execution(* cn..impl.*.*(cn.bruce.domain.Account))") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ //得到方法執(zhí)行所需的參數(shù) Object[] args = pjp.getArgs(); System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。前置"); //明確調(diào)用業(yè)務(wù)層方法(切入點方法) rtValue = pjp.proceed(args); System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。異常"); throw new RuntimeException(t); }finally { System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。最終"); } } }
⑤ 書寫測試類進(jìn)行測試
public class TestAOP { public static void main(String[] args) { ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class); TestService testService = (TestService) ac.getBean("testService"); testService.testOfInt(133); System.out.println("-----------"); testService.testOfInteger(112); System.out.println("-----------"); testService.testOfVoid(); System.out.println("-----------"); Account account = (Account) ac.getBean("account"); account.setName("Bruce"); account.setAge(112); testService.testOfAccount(account); } }
“spring框架入門之怎么使用切面編程AOP”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
分享文章:spring框架入門之怎么使用切面編程AOP
網(wǎng)站地址:http://www.rwnh.cn/article44/jdciee.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、品牌網(wǎng)站建設(shè)、企業(yè)網(wǎng)站制作、電子商務(wù)、做網(wǎng)站、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)