摘要:面向切面的程序設(shè)計(jì)思想也是面向切面軟件開(kāi)發(fā)的基礎(chǔ)。與切面相關(guān)的編程概念還包括元對(duì)象協(xié)議主題混入和委托。切面聲明類(lèi)似于中的類(lèi)聲明,在中會(huì)包含著一些以及相應(yīng)的。
spring-boot-aop 什么是aop
面向切面的程序設(shè)計(jì)(Aspect-oriented programming,AOP,又譯作面向方面的程序設(shè)計(jì)、剖面導(dǎo)向程序設(shè)計(jì))是計(jì)算機(jī)科學(xué)中的一種程序設(shè)計(jì)思想,旨在將橫切關(guān)注點(diǎn)與業(yè)務(wù)主體進(jìn)行進(jìn)一步分離,以提高程序代碼的模塊化程度。通過(guò)在現(xiàn)有代碼基礎(chǔ)上增加額外的通知(Advice)機(jī)制,能夠?qū)Ρ宦暶鳛椤扒悬c(diǎn)(Pointcut)”的代碼塊進(jìn)行統(tǒng)一管理與裝飾,如“對(duì)所有方法名以‘set*’開(kāi)頭的方法添加后臺(tái)日志”。該思想使得開(kāi)發(fā)人員能夠?qū)⑴c代碼核心業(yè)務(wù)邏輯關(guān)系不那么密切的功能(如日志功能)添加至程序中,同時(shí)又不降低業(yè)務(wù)代碼的可讀性。面向切面的程序設(shè)計(jì)思想也是面向切面軟件開(kāi)發(fā)的基礎(chǔ)。
面向切面的程序設(shè)計(jì)將代碼邏輯切分為不同的模塊(即關(guān)注點(diǎn)(Concern),一段特定的邏輯功能)。幾乎所有的編程思想都涉及代碼功能的分類(lèi),將各個(gè)關(guān)注點(diǎn)封裝成獨(dú)立的抽象模塊(如函數(shù)、過(guò)程、模塊、類(lèi)以及方法等),后者又可供進(jìn)一步實(shí)現(xiàn)、封裝和重寫(xiě)。部分關(guān)注點(diǎn)“橫切”程序代碼中的數(shù)個(gè)模塊,即在多個(gè)模塊中都有出現(xiàn),它們即被稱(chēng)作“橫切關(guān)注點(diǎn)(Cross-cutting concerns, Horizontal concerns)”。
日志功能即是橫切關(guān)注點(diǎn)的一個(gè)典型案例,因?yàn)槿罩竟δ芡鶛M跨系統(tǒng)中的每個(gè)業(yè)務(wù)模塊,即“橫切”所有有日志需求的類(lèi)及方法體。而對(duì)于一個(gè)信用卡應(yīng)用程序來(lái)說(shuō),存款、取款、帳單管理是它的核心關(guān)注點(diǎn),日志和持久化將成為橫切整個(gè)對(duì)象結(jié)構(gòu)的橫切關(guān)注點(diǎn)。
切面的概念源于對(duì)面向?qū)ο蟮某绦蛟O(shè)計(jì)的改進(jìn),但并不只限于此,它還可以用來(lái)改進(jìn)傳統(tǒng)的函數(shù)。與切面相關(guān)的編程概念還包括元對(duì)象協(xié)議、主題(Subject)、混入(Mixin)和委托(Delegate)。
AOP中的相關(guān)概念看過(guò)了上面解釋?zhuān)氡卮蠹覍?duì)aop已經(jīng)有個(gè)大致的雛形了,但是又對(duì)上面提到的切面之類(lèi)的術(shù)語(yǔ)有一些模糊的地方,接下來(lái)就來(lái)講解一下AOP中的相關(guān)概念,了解了AOP中的概念,才能真正的掌握AOP的精髓。
Aspect(切面): Aspect 聲明類(lèi)似于 Java 中的類(lèi)聲明,在 Aspect 中會(huì)包含著一些 Pointcut 以及相應(yīng)的 Advice。
Joint point(連接點(diǎn)):表示在程序中明確定義的點(diǎn),典型的包括方法調(diào)用,對(duì)類(lèi)成員的訪(fǎng)問(wèn)以及異常處理程序塊的執(zhí)行等等,它自身還可以嵌套其它 joint point。
Pointcut(切點(diǎn)):表示一組 joint point,這些 joint point 或是通過(guò)邏輯關(guān)系組合起來(lái),或是通過(guò)通配、正則表達(dá)式等方式集中起來(lái),它定義了相應(yīng)的 Advice 將要發(fā)生的地方。
Advice(增強(qiáng)):Advice 定義了在 Pointcut 里面定義的程序點(diǎn)具體要做的操作,它通過(guò) before、after 和 around 來(lái)區(qū)別是在每個(gè) joint point 之前、之后還是代替執(zhí)行的代碼。
Target(目標(biāo)對(duì)象):織入 Advice 的目標(biāo)對(duì)象.。
Weaving(織入):將 Aspect 和其他對(duì)象連接起來(lái), 并創(chuàng)建 Adviced object 的過(guò)程
spring aopSpring AOP使用純Java實(shí)現(xiàn),它不需要專(zhuān)門(mén)的編譯過(guò)程,也不需要特殊的類(lèi)裝載器,它在運(yùn)行期通過(guò)代理方式向目標(biāo)類(lèi)織入增強(qiáng)代碼。在Spring中可以無(wú)縫地將Spring AOP、IoC和AspectJ整合在一起。Spring AOP構(gòu)建在動(dòng)態(tài)代理基礎(chǔ)之上,因此,Spring對(duì)AOP的支持局限于方法攔截。在Java中動(dòng)態(tài)代理有兩種方式:JDK動(dòng)態(tài)代理和CGLib動(dòng)態(tài)代理
jdk proxy
java動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類(lèi),在調(diào)用具體方法前調(diào)用InvokeHandler來(lái)處理。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** ** * @author leone * @since 2018-11-09 **/ public class JdkProxy { interface IUserService { Integer delete(Integer userId); } static class UserServiceImpl implements IUserService { @Override public Integer delete(Integer userId) { // 業(yè)務(wù) System.out.println("delete user"); return userId; } } // 自定義InvocationHandler static class UserServiceProxy implements InvocationHandler { // 目標(biāo)對(duì)象 private Object target; public UserServiceProxy(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("------方法調(diào)用前---------"); //執(zhí)行相應(yīng)的目標(biāo)方法 Object result = method.invoke(target, args); System.out.println("------方法調(diào)用后---------"); return result; } } public static void main(String[] args) { IUserService userService = new UserServiceImpl(); // 創(chuàng)建調(diào)用處理類(lèi) UserServiceProxy handler = new UserServiceProxy(userService); // 得到代理類(lèi)實(shí)例 IUserService proxy = (IUserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), new Class[]{IUserService.class}, handler); // 調(diào)用代理類(lèi)的方法 Integer userId = proxy.delete(3); System.out.println(userId); } }
cglib proxy
而cglib動(dòng)態(tài)代理是利用asm開(kāi)源包,對(duì)代理對(duì)象類(lèi)的class文件加載進(jìn)來(lái),通過(guò)修改其字節(jié)碼生成子類(lèi)來(lái)處理。
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** ** * @author leone * @since 2018-11-09 **/ public class CglibProxy { static class UserService implements MethodInterceptor { private Object target; /** * 業(yè)務(wù)方法 * * @param userId * @return */ public Integer delete(Integer userId) { System.out.println("delete user"); return userId; } /** * 利用Enhancer類(lèi)生成代理類(lèi) * * @param target * @return */ public Object getInstance(Object target) { this.target = target; // 創(chuàng)建加強(qiáng)器,用來(lái)創(chuàng)建動(dòng)態(tài)代理類(lèi) Enhancer enhancer = new Enhancer(); // 為加強(qiáng)器指定要代理的業(yè)務(wù)類(lèi)(即:為下面生成的代理類(lèi)指定父類(lèi)) enhancer.setSuperclass(target.getClass()); // 設(shè)置回調(diào):對(duì)于代理類(lèi)上所有方法的調(diào)用,都會(huì)調(diào)用CallBack,而Callback則需要實(shí)現(xiàn)intercept()方法進(jìn)行攔 enhancer.setCallback(this); // 創(chuàng)建動(dòng)態(tài)代理類(lèi)對(duì)象并返回 return enhancer.create(); } /** * @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 { System.out.println("------方法調(diào)用前---------"); Object object = methodProxy.invokeSuper(o, objects); System.out.println("------方法調(diào)用后---------"); return object; } } public static void main(String[] args) { UserService userService = new UserService(); UserService proxy = (UserService) userService.getInstance(userService); Integer userId = proxy.delete(2); System.out.println(userId); } }
1、如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP
2、如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)了接口,必須采用CGLIB庫(kù),spring會(huì)自動(dòng)在JDK動(dòng)態(tài)代理和CGLIB之間轉(zhuǎn)換
Advice的主要類(lèi)型org.springframework.boot spring-boot-starter-aop
@Before:該注解標(biāo)注的方法在業(yè)務(wù)模塊代碼執(zhí)行之前執(zhí)行,其不能阻止業(yè)務(wù)模塊的執(zhí)行,除非拋出異常;
@AfterReturning:該注解標(biāo)注的方法在業(yè)務(wù)模塊代碼執(zhí)行之后執(zhí)行;
@AfterThrowing:該注解標(biāo)注的方法在業(yè)務(wù)模塊拋出指定異常后執(zhí)行;
@After:該注解標(biāo)注的方法在所有的Advice執(zhí)行完成后執(zhí)行,無(wú)論業(yè)務(wù)模塊是否拋出異常,類(lèi)似于finally的作用;
@Around:該注解功能最為強(qiáng)大,其所標(biāo)注的方法用于編寫(xiě)包裹業(yè)務(wù)模塊執(zhí)行的代碼,其可以傳入一個(gè)ProceedingJoinPoint用于調(diào)用業(yè)務(wù)模塊的代碼,無(wú)論是調(diào)用前邏輯還是調(diào)用后邏輯,都可以在該方法中編寫(xiě),甚至其可以根據(jù)一定的條件而阻斷業(yè)務(wù)模塊的調(diào)用;
@DeclareParents:其是一種Introduction類(lèi)型的模型,在屬性聲明上使用,主要用于為指定的業(yè)務(wù)模塊添加新的接口和相應(yīng)的實(shí)現(xiàn)。
切點(diǎn)表達(dá)式1.通配符
[*] 匹配任意字符,但只能匹配一個(gè)元素
[..] 匹配任意字符,可以匹配任意多個(gè)元素,表示類(lèi)時(shí),必須和*聯(lián)合使用
[+] 必須跟在類(lèi)名后面,如Horseman+,表示類(lèi)本身和繼承或擴(kuò)展指定類(lèi)的所有類(lèi)
2.邏輯運(yùn)算符
表達(dá)式可由多個(gè)切點(diǎn)函數(shù)通過(guò)邏輯運(yùn)算組成
&& 與操作,求交集,也可以寫(xiě)成and
例如?execution(* chop(..)) && target(Horseman) ?表示Horseman及其子類(lèi)的chop方法
|| 或操作,任一表達(dá)式成立即為true,也可以寫(xiě)成 or
例如?execution(* chop(..)) || args(String) ?表示名稱(chēng)為chop的方法或者有一個(gè)String型參數(shù)的方法
! 非操作,表達(dá)式為false則結(jié)果為true,也可以寫(xiě)成 not
例如?execution(* chop(..)) and !args(String)??表示名稱(chēng)為chop的方法但是不能是只有一個(gè)String型參數(shù)的方法
execution() 方法匹配模式串
表示滿(mǎn)足某一匹配模式的所有目標(biāo)類(lèi)方法連接點(diǎn)。如execution(* save(..))表示所有目標(biāo)類(lèi)中的 save()方法。
由于Spring切面粒度最小是達(dá)到方法級(jí)別,而execution表達(dá)式可以用于明確指定方法返回類(lèi)型,類(lèi)名,方法名和參數(shù)名等與方法相關(guān)的部件,并且在Spring中,大部分需要使用AOP的業(yè)務(wù)場(chǎng)景也只需要達(dá)到方法級(jí)別即可,因而execution表達(dá)式的使用是最為廣泛的。如下是execution表達(dá)式的語(yǔ)法
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(<修飾符> <返回類(lèi)型> <類(lèi)路徑> <方法名>(<參數(shù)列表>) <異常模式> )
modifiers-pattern:方法的可見(jiàn)性,如public,protected;
ret-type-pattern:方法的返回值類(lèi)型,如int,void等;
declaring-type-pattern:方法所在類(lèi)的全路徑名,如com.spring.Aspect;
name-pattern:方法名類(lèi)型,如buisinessService();
param-pattern:方法的參數(shù)類(lèi)型,如java.lang.String;
throws-pattern:方法拋出的異常類(lèi)型,如java.lang.Exception;
切點(diǎn)函數(shù)@annotation(annotation-type) 方法注解類(lèi)名
如下示例表示匹配使用com.leone.aop.AopTest注解標(biāo)注的方法:
@annotation(com.leone.aop.AopTest)
args(param-pattern) 方法入?yún)⑶悬c(diǎn)函數(shù)
如下示例表示匹配所有只有一個(gè)參數(shù),并且參數(shù)類(lèi)型是java.lang.String類(lèi)型的方法:
args(java.lang.String)
@args(annotation-type) 方法入?yún)㈩?lèi)注解切點(diǎn)函數(shù)
如下示例表示匹配使用了com.leone.aop.AopTest注解標(biāo)注的類(lèi)作為參數(shù)的方法:
@args(com.leone.aop.AopTest)
within(declaring-type-pattern) 類(lèi)名匹配切點(diǎn)函數(shù)
within表達(dá)式只能指定到類(lèi)級(jí)別,如下示例表示匹配com.leone.aop.UserService中的所有方法:
within(com.leone.aop.UserService)
@within(annotation-type) 類(lèi)注解匹配切點(diǎn)函數(shù)
如下示例表示匹配使用org.springframework.web.bind.annotation.RestController注解標(biāo)注的類(lèi):
@within(org.springframework.web.bind.annotation.RestController)
target(declaring-type-pattern) 類(lèi)名切點(diǎn)函數(shù)
如下示例表示匹配com.leone.aop.UserService中的所有方法:
target(com.leone.aop.UserService)
this
spring-boot-aop 實(shí)戰(zhàn)配置切面類(lèi),實(shí)現(xiàn)代理
1.在類(lèi)上使用 @Component 注解把切面類(lèi)加入到IOC容器中
2.在類(lèi)上使用 @Aspect 注解使之成為切面類(lèi)
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * 描述一個(gè)切面類(lèi) * * @author leone * @since 2018-06-21 **/ @Slf4j @Aspect @Component public class AopConfig { /** * 1.通配符 * [*] 匹配任意字符,但只能匹配一個(gè)元素 ** [..] 匹配任意字符,可以匹配任意多個(gè)元素,表示類(lèi)時(shí),必須和*聯(lián)合使用 *
* [+] 必須跟在類(lèi)名后面,如Horseman+,表示類(lèi)本身和繼承或擴(kuò)展指定類(lèi)的所有類(lèi) *
* 切點(diǎn)表達(dá)式分為 修飾符 返回類(lèi)型 包路徑 方法名 參數(shù) *
* 2.切點(diǎn)表達(dá)式 *
* 3.邏輯運(yùn)算符 * 表達(dá)式可由多個(gè)切點(diǎn)函數(shù)通過(guò)邏輯運(yùn)算組成 * ** && 與操作,求交集,也可以寫(xiě)成and *
* 例如?execution(* chop(..)) && target(Horseman) ?表示Horseman及其子類(lèi)的chop方法 *
* ** || 或操作,任一表達(dá)式成立即為true,也可以寫(xiě)成 or *
* 例如?execution(* chop(..)) || args(String) ?表示名稱(chēng)為chop的方法或者有一個(gè)String型參數(shù)的方法 *
* ** ! 非操作,表達(dá)式為false則結(jié)果為true,也可以寫(xiě)成 not *
* 例如?execution(* chop(..)) and !args(String)??表示名稱(chēng)為chop的方法但是不能是只有一個(gè)String型參數(shù)的方法 */ @Pointcut("execution(* com.leone.boot.aop.service.*.*(..))") public void pointCut() { } /** * 環(huán)繞通知在 target 開(kāi)始和結(jié)束執(zhí)行 * * @param point * @return */ @Around(value = "pointCut()") public Object around(ProceedingJoinPoint point) { long start = System.currentTimeMillis(); String methodName = point.getSignature().getName(); log.info("around method name: {} params: {}", methodName, Arrays.asList(point.getArgs())); try { log.info("around end time: {}", (System.currentTimeMillis() - start) + " ms!"); return point.proceed(); } catch (Throwable e) { log.error("message: {}", e.getMessage()); } return null; } /** * 前置通知在 target 前執(zhí)行 * * @param joinPoint */ // @Before("@annotation(com.leone.boot.aop.anno.AopBefore)") // @Before("within(com.leone.boot.aop.controller.*)") // @Before("@within(org.springframework.web.bind.annotation.RestController)") // @Before("target(com.leone.boot.aop.controller.UserController)") @Before("@target(com.leone.boot.aop.anno.ClassAop) && @annotation(com.leone.boot.aop.anno.AopBefore)") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List
測(cè)試類(lèi)
import com.leone.boot.aop.anno.AopBefore; import com.leone.boot.aop.anno.ClassAop; import com.leone.boot.aop.interf.UserService; import com.leone.boot.common.entity.User; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * @author leone * @since 2018-06-21 **/ @Slf4j @ClassAop @RestController @RequestMapping("/api") public class UserController { private UserService userService; public UserController(UserService userService) { this.userService = userService; } @AopBefore @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) public User findOne(@PathVariable Long userId) { return userService.findOne(userId); } @AopBefore @RequestMapping("/user") public User save(User user) { return user; } }
github
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74627.html
摘要:通知和切點(diǎn)共同定義了關(guān)于切面的全部?jī)?nèi)容,它是什么時(shí)候,在何時(shí)和何處完成功能引入允許我們向現(xiàn)有的類(lèi)添加新的方法或者屬性組裝方面來(lái)創(chuàng)建一個(gè)被通知對(duì)象。這可以在編譯時(shí)完成例如使用編譯器,也可以在運(yùn)行時(shí)完成。和其他純框架一樣,在運(yùn)行時(shí)完成織入。 原文:190301-SpringBoot基礎(chǔ)篇AOP之基本使用姿勢(shì)小結(jié) 一般來(lái)講,談到Spring的特性,繞不過(guò)去的就是DI(依賴(lài)注入)和AOP(切...
摘要:我們會(huì)寫(xiě)切面來(lái)攔截對(duì)這些業(yè)務(wù)類(lèi)和類(lèi)的調(diào)用。切面定義何時(shí)攔截一個(gè)方法以及做什么和在一起成為切面連接點(diǎn)當(dāng)代碼開(kāi)始執(zhí)行,并且切點(diǎn)的條件滿(mǎn)足時(shí),通知被調(diào)用。 前言 這篇文章會(huì)幫助你使用Spring Boot Starter AOP實(shí)現(xiàn)AOP。我們會(huì)使用AspectJ實(shí)現(xiàn)四個(gè)不同的通知(advice),并且新建一個(gè)自定義的注解來(lái)追蹤方法的執(zhí)行時(shí)間。 你將會(huì)了解 什么是交叉分割關(guān)注點(diǎn)(cross...
摘要:首先先來(lái)看我們事先定義的以及。可以看到會(huì)修改方法的返回值,使其返回。例子測(cè)試的行為最簡(jiǎn)單的測(cè)試方法就是直接調(diào)用,看看它是否使用返回。先看這段代碼這些是利用提供的和來(lái)判斷是否被代理了的實(shí)現(xiàn)是通過(guò)動(dòng)態(tài)代理來(lái)做的。 Github地址 Spring提供了一套AOP工具,但是當(dāng)你把各種Aspect寫(xiě)完之后,如何確定這些Aspect都正確的應(yīng)用到目標(biāo)Bean上了呢?本章將舉例說(shuō)明如何對(duì)Spring...
摘要:示例代碼如下添加的設(shè)置默認(rèn)的配置對(duì)應(yīng)的是原來(lái)的如何使用注解從主庫(kù)到備庫(kù)的切換 摘要: 本篇文章的場(chǎng)景是做調(diào)度中心和監(jiān)控中心時(shí)的需求,后端使用TDDL實(shí)現(xiàn)分表分庫(kù),需求:實(shí)現(xiàn)關(guān)鍵業(yè)務(wù)的查詢(xún)監(jiān)控,當(dāng)用Mybatis查詢(xún)數(shù)據(jù)時(shí)需要從主庫(kù)切換到備庫(kù)或者直接連到備庫(kù)上查詢(xún),從而減小主庫(kù)的壓力,在本篇文章中主要記錄在Spring Boot中通過(guò)自定義注解結(jié)合AOP實(shí)現(xiàn)直接連接備庫(kù)查詢(xún)。 一.通過(guò)A...
摘要:接口日志有啥用在我們?nèi)粘5拈_(kāi)發(fā)過(guò)程中,我們可以通過(guò)接口日志去查看這個(gè)接口的一些詳細(xì)信息。在切入點(diǎn)返回內(nèi)容之后切入內(nèi)容可以用來(lái)對(duì)處理返回值做一些加工處理。 接口日志有啥用 在我們?nèi)粘5拈_(kāi)發(fā)過(guò)程中,我們可以通過(guò)接口日志去查看這個(gè)接口的一些詳細(xì)信息。比如客戶(hù)端的IP,客戶(hù)端的類(lèi)型,響應(yīng)的時(shí)間,請(qǐng)求的類(lèi)型,請(qǐng)求的接口方法等等,我們可以對(duì)這些數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析,提取出我們想要的信息。 怎么拿到接口...
閱讀 2213·2021-10-18 13:28
閱讀 2529·2021-10-11 10:59
閱讀 2352·2019-08-29 15:06
閱讀 1142·2019-08-26 13:54
閱讀 821·2019-08-26 13:52
閱讀 3156·2019-08-26 12:02
閱讀 3009·2019-08-26 11:44
閱讀 2521·2019-08-26 10:56