您好,登錄后才能下訂單哦!
本篇內容介紹了“spring框架入門之怎么使用切面編程AOP”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
特點:字節碼隨用隨創建,隨用隨加載
作用:在不修改源碼的基礎上對方法進行增強
分類:
基于接口的動態代理 | 基于子類的動態代理 | |
---|---|---|
涉及的類 | Proxy | Enhancer |
提供者 | JDK官方 | 第三方庫cglib |
如何創建代理對象 | 使用Proxy中的 newProxyInstance 方法 | 使用Enhancer類中的create 方法 |
創建代理對象的要求 | 被代理類至少實現一個接口,沒有則不能使用 | 被代理對象不能是最終類 |
newProxyInstance方法的參數:
ClassLoader : 類加載器。用于加載代理對象字節碼,和被代理類使用相同的類加載器。
寫法: 代理對象.getClass().getClassLoader()
Class[] : 字節碼數組。用于讓代理對象和被代理對象有相同方法。
寫法: 代理對象.getClass().getInterfaces()
InvocationHandler:用于增強的代碼。書寫對被代理方法增強的代碼,一般書寫此接口的實現類,通常情況下是匿名內部類,但不是必須的,此接口的實現類一般誰用到誰寫。
InvocationHandler參數中的invoke方法,執行被代理對象的任何接口方法都會經過該方法。方法參數及其含義:
proxy :代理對象的引用
method :當前執行的方法
args:當前執行方法所需的參數
返回值:與被代理類有相同的返回值
代碼示例:
public class Client { public static void main(String[] args) { final ProducerImpl producer = new ProducerImpl(); producer.saleProduct(1000f);// 銷售產品,拿到錢1000.0 System.out.println("對方法進行增強后。。。。。"); Producer proxyProduct = (Producer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 執行被代理對象的任何接口方法都會經過該方法 * 方法的參數含義 * @param proxy 代理對象的引用 * @param method 當前執行方法 * @param args 當前執行方法所需的參數 * @return 和被代理對象有相同的返回值 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 提供增強的代碼 Object returnValue = null; // 1.獲取方法的執行參數 Float money = (Float) args[0]; // 2.判斷當前方法是不是銷售方法 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer, money * 0.8f); } return returnValue; } }); proxyProduct.saleProduct(1000f);// 銷售產品,拿到錢800.0 } }
create方法的參數:
Class:字節碼。用于指定被代理對象的字節碼。
Callback:用于提供增強的代碼,類似于基于接口的動態代理的invoke
方法。一般寫的是該接口的子接口實現類 MethodInterceptor
create參數中 MethodInterceptor
的方法參數:
o :代理對象的引用
method :當前執行的方法
objects:當前執行方法所需的參數
methodProxy:當前執行方法的代理對象
代碼示例:
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("對方法進行增強后。。。。。"); Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 執行任何被處理對象的任何方法都會經過該方法 * @param o 代理對象的引用 * @param method 當前的執行方法 * @param objects 當前執行方法所需的參數 * @param methodProxy 當前執行方法的代理對象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 提供增強的方法 Object returnValue = null; // 1.獲取當前方法的執行參數 Float money = (Float) objects[0]; // 2.判斷當前的方法是不是銷售動作 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer, money * 0.8f); } return returnValue; } }); cglibProducer.saleProduct(1000f);// 售賣商品,得到錢800.0 } }
動態代理的一般使用方式:
獲取被代理類對象(被代理對象的字節碼、被代理類對象的類加載器等信息)
在代理類提供的方法中對被代理類中的方法進行增強
spring中的AOP是通過配置的方式實現動態代理
**Joinpoint(連接點):**指被攔截到的點。在spring中這些點指的是方法,因為spring只支持方法類型的連接點。可以理解為業務層中所有的方法。
**Pointcut(切入點):**指需要對那些Joinpoint進行攔截的定義。可以理解為被增強的方法。
**Advice(通知/增強):**指攔截到Joinpoint后需要做的事情。通知類型:前置通知,后置通知,異常通知,最終通知,環繞通知。
前置通知:在執行業務層方法前的通知;
后置通知:在執行業務層方法后的通知;
異常通知:catch中的通知;
最終通知:在finally中的通知;
環繞通知:整個invoke方法執行就是環繞通知;
**Introduction(引介):**一種特殊的通知在不修改類代碼的前提下,Introduction可以在運行期為類動態的添加一些方法或Field。
Target(目標對象):代理的目標對象。
**Weaving(織入):**指把增強應用到目標對象來創建代理對象的過程。spring是動態代理織入的,而AspectJ采用編譯期織入和類裝載期織入。
**Proxy(代理):**一個類被AOP織入增強后,就產生一個結果代理類。
**Aspect(切面):**是切入點和通知(引介)的結合。
開發階段:
編寫核心業務代碼(主線開發,熟悉業務代碼即可進行開發)
把公共代碼提取出來,制作成通知。(開發最后階段)
在配置文件中聲明切入點與通知之間的關系,即切面。
運行階段:
spring框架監控切入點的方法執行。一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的代碼邏輯運行。
spring中的AOP會根據目標是否實現了接口來決定采用哪種動態代理的方式
3.1 將通知類交由IoC容器管理
將通知類注冊到spring的IoC容器中
<bean id="" class=""> <property name="" ref=""></property> </bean>
3.2 使用 <aop:config>
標簽進行AOP配置
用于聲明aop配置
<aop:config> <!-- 配置的代碼寫在此處 --> </aop:config>
3.3 使用 <aop:aspect>
配置切面
用于配置切面
屬性:
① id屬性:是給切面提供一個唯一標識
② ref屬性:是指定通知類bean的Id。
<aop:aspect id="" ref=""> <!-- 在這里配置通知類型 --> </aop:aspect>
<aop:before>
:用于配置前置通知
<aop:after-return>
:用于配置后置通知
<aop:after-throwing>
:用于配置異常通知
<aop:after>
:用于配置最終通知
<aop: around>
:用于配置環繞通知
① method屬性:用于指定Logger類中哪個方法是前置通知
② pointcut屬性:用于指定切入點表達式,該表達式的含義指的是對業務層中哪些方法增強
③ pointcut-ref屬性:用于指定切入點表達式的id
3.4 使用 <aop:pointcut>
配置切入點表達式
用于配置切入點表達式,就是指定對那些類進行的那些方法進行增強
屬性:
① id屬性:用于指定切入點的唯一標識
② expression屬性:用于配置切入點表達式
<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"> <!-- 配置通知類型,建立通知方法和切入點方法的關聯 --> <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>
關鍵字:execution("表達式")
表達式寫法:訪問修飾符 返回值 包名.***.包名.類名.方法名(參數列表)
標準寫法:public void cn.bruce.service.impl.AccountServiceImpl.saveAccount()
訪問修飾符可以省略(訪問權限不能寫 *
),表示匹配任意類型的訪問權限,但Spring現在只支持public權限;
void cn.bruce.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值;
* cn.bruce.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包,有幾級包就要寫幾個 *
;
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用 ..
表示當前包及其子包
* cn..AccountServiceImpl.saveAccount()
類名和方法名都可以使用通配符代替
* *..*.*()
**參數列表:**直接寫數據類型
基本數據類型直接寫名稱,如:int long double boolean
引用數據類型要寫全類名,如:cn.bruce.domain.Accout
可以使用通配符 *
表示任意類型,但是必須有參數
可以使用通配符 *
進行占位,如:* *..*.*(*, int)
可以使用 ..
表示有無參數均可,有參數可以是任意類型 * *..*.*(..)
全通配寫法:* *..*.*(..)
開發中切入點表達式的通常寫法:如:切到業務層實現類下的所有方法 * cn.bruce.service.impl.*.*(..)
前置通知 <aop:before>
:在切入點方法執行之前執行
后置通知 <aop:after-returning>
:在切入點方法執行之后執行。后置通知和異常通知永遠只能執行一個
異常通知 <aop:after-throwing>
:在切入點方法執行產生異常后執行。異常通知和后置通知永遠只能執行一個
最終通知 <aop:after>
:無論切入點方法是否正常執行,它都會在其后面執行
環繞通知 <aop:around>
:是spring框架為我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。
代碼示例:
<!-- 配置AOP --> <aop:config> <!-- 配置切入點表達式,id屬性是表達式的唯一標識,expression屬性用于指定表達式內容 此標簽可以寫在aop:aspect標簽內部只能當前切面使用 寫在aop:aspect外面,此時表示所有的切面可用 --> <aop:pointcut id="loggerPointCut" expression="execution(* cn..impl.Account*.*(..))"/> <!-- 配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 前置通知:在切入點方法執行之前執行 --> <aop:before method="beforePrintLog" pointcut-ref="loggerPointCut"></aop:before> <!-- 后置通知:在切入點方法執行之后執行。后置通知和異常通知永遠只能執行一個 --> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="loggerPointCut"></aop:after-returning> <!-- 異常通知:在切入點方法執行產生異常后執行。異常通知和后置通知永遠只能執行一個 --> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="loggerPointCut"></aop:after-throwing> <!-- 最終通知:無論切入點方法是否正常執行,它都會在其后面執行 --> <aop:after method="afterPrintLog" pointcut-ref="loggerPointCut"></aop:after> <!-- 環繞通知:是spring框架為我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。 --> <aop:around method="aroundPrintLog" pointcut-ref="loggerPointCut"></aop:around> </aop:aspect> </aop:config>
配置步驟:
①導入maven坐標
<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) // 開啟注解驅動 public class SpringConfiguration { }
③ 將業務層實體類交由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); } }
④ 書寫切面類,聲明為切面類并設置切入點和通知類型
@Component("logger") @Aspect // 表示此類為切面類 public class Logger { @Pointcut("execution(* cn..impl.*.*(..))") // 指定切入點表達式 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方法開始記錄日志了。。。"); } /** * 環繞通知 */ @Around("execution(* cn..impl.*.*(cn.bruce.domain.Account))") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ //得到方法執行所需的參數 Object[] args = pjp.getArgs(); System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。前置"); //明確調用業務層方法(切入點方法) 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方法開始記錄日志了。。。最終"); } } }
⑤ 書寫測試類進行測試
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”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。