Java中的SpringAOP、代理模式、常用AspectJ注解詳解
一、AOP簡述
回到主題,何為AOP?AOP即面向切面編程——Spring提供了面向切面編程的豐富支持,允許通過分離應(yīng)用的業(yè)務(wù)邏輯與系統(tǒng)級(jí)服務(wù)(例如審計(jì)(auditing)和事務(wù)(transaction)管理)進(jìn)行內(nèi)聚性的開發(fā)。
應(yīng)用對(duì)象只實(shí)現(xiàn)它們應(yīng)該做的——完成業(yè)務(wù)邏輯——僅此而已。
它們并不負(fù)責(zé)(甚至是意識(shí))其它的系統(tǒng)級(jí)關(guān)注點(diǎn),例如日志或事務(wù)支持。
如下圖,可以很直接明了的展示整個(gè)AOP的過程:
1.1 一些基本概念
通知(Adivce)
通知有5種類型:
我們可能會(huì)問,那通知對(duì)應(yīng)系統(tǒng)中的代碼是一個(gè)方法、對(duì)象、類、還是接口什么的呢?
我想說一點(diǎn),其實(shí)都不是,你可以理解通知就是對(duì)應(yīng)我們?nèi)粘I钪兴f的通知,比如‘某某人,你2019年9月1號(hào)來學(xué)校報(bào)個(gè)到’,通知更多地體現(xiàn)一種告訴我們(告訴系統(tǒng)何)何時(shí)執(zhí)行,規(guī)定一個(gè)時(shí)間,在系統(tǒng)運(yùn)行中的某個(gè)時(shí)間點(diǎn)(比如拋異常啦!方法執(zhí)行前啦?。?并非對(duì)應(yīng)代碼中的方法!并非對(duì)應(yīng)代碼中的方法!并非對(duì)應(yīng)代碼中的方法!
- Before 在方法被調(diào)用之前調(diào)用
- After 在方法完成后調(diào)用通知,無論方法是否執(zhí)行成功
- After-returning 在方法成功執(zhí)行之后調(diào)用通知
- After-throwing 在方法拋出異常后調(diào)用通知
- Around 通知了好、包含了被通知的方法,在被通知的方法調(diào)用之前后調(diào)用之后執(zhí)行自定義的行為
- 切點(diǎn)(Pointcut)
- 切點(diǎn)在Spring AOP中確實(shí)是對(duì)應(yīng)系統(tǒng)中的方法。但是這個(gè)方法是定義在切面中的方法,一般和通知一起使用,一起組成了切面。
- 連接點(diǎn)(Join point)
- 比如:方法調(diào)用、方法執(zhí)行、字段設(shè)置/獲取、異常處理執(zhí)行、類初始化、甚至是 for 循環(huán)中的某個(gè)點(diǎn) 理論上, 程序執(zhí)行過程中的任何時(shí)點(diǎn)都可以作為作為織入點(diǎn), 而所有這些執(zhí)行時(shí)點(diǎn)都是 Joint point 但 Spring AOP 目前僅支持方法執(zhí)行 (method execution) 也可以這樣理解,連接點(diǎn)就是你準(zhǔn)備在系統(tǒng)中執(zhí)行切點(diǎn)和切入通知的地方(一般是一個(gè)方法,一個(gè)字段)
- 切面(Aspect)
- 切面是切點(diǎn)和通知的集合,一般單獨(dú)作為一個(gè)類。通知和切點(diǎn)共同定義了關(guān)于切面的全部內(nèi)容,它是什么時(shí)候,在何時(shí)和何處完成功能。
- 引入(Introduction)
- 引用允許我們向現(xiàn)有的類添加新的方法或者屬性
- 織入(Weaving)
- 組裝方面來創(chuàng)建一個(gè)被通知對(duì)象。這可以在編譯時(shí)完成(例如使用AspectJ編譯器),也可以在運(yùn)行時(shí)完成。Spring和其他純Java AOP框架一樣,在運(yùn)行時(shí)完成織入。
二、代理模式
首先AOP思想的實(shí)現(xiàn)一般都是基于代理模式,在JAVA中一般采用JDK動(dòng)態(tài)代理模式,但是我們都知道,JDK動(dòng)態(tài)代理模式只能代理接口,如果要代理類那么就不行了。
因此,Spring AOP 會(huì)這樣子來進(jìn)行切換,因?yàn)镾pring AOP 同時(shí)支持 CGLIB、ASPECTJ、JDK動(dòng)態(tài)代理,當(dāng)你的真實(shí)對(duì)象有實(shí)現(xiàn)接口時(shí),Spring AOP會(huì)默認(rèn)采用JDK動(dòng)態(tài)代理,否則采用cglib代理。
- 如果目標(biāo)對(duì)象的實(shí)現(xiàn)類實(shí)現(xiàn)了接口,Spring AOP 將會(huì)采用 JDK 動(dòng)態(tài)代理來生成 AOP 代理類;
- 如果目標(biāo)對(duì)象的實(shí)現(xiàn)類沒有實(shí)現(xiàn)接口,Spring AOP 將會(huì)采用 CGLIB 來生成 AOP 代理類——不過這個(gè)選擇過程對(duì)開發(fā)者完全透明、開發(fā)者也無需關(guān)心。
這里簡單說說代理模式,代理模式的UML類圖如下:
2.1 靜態(tài)代理
//接口類: interface Person { void speak(); } //真實(shí)實(shí)體類: class Actor implements Person { private String content; public Actor(String content) { this.content = content; } @Override public void speak() { System.out.println(this.content); } } //代理類: class Agent implements Person { private Actor actor; private String before; private String after; public Agent(Actor actor, String before, String after) { this.actor = actor; this.before = before; this.after = after; } @Override public void speak() { //before speak System.out.println("Before actor speak, Agent say: " + before); //real speak this.actor.speak(); //after speak System.out.println("After actor speak, Agent say: " + after); } } //測試方法: public class StaticProxy { public static void main(String[] args) { Actor actor = new Actor("I am a famous actor!"); Agent agent = new Agent(actor, "Hello I am an agent.", "That's all!"); agent.speak(); } }
2.2 動(dòng)態(tài)代理
在講JDK的動(dòng)態(tài)代理方法之前,不妨先想想如果讓你來實(shí)現(xiàn)一個(gè)可以任意類的任意方法的代理類,該怎么實(shí)現(xiàn)?有個(gè)很naive的做法,通過反射獲得Class和Method,再調(diào)用該方法,并且實(shí)現(xiàn)一些代理的方法。我嘗試了一下,很快就發(fā)現(xiàn)問題所在了。于是乎,還是使用JDK的動(dòng)態(tài)代理接口吧。
JDK自帶方法
首先介紹一下最核心的一個(gè)接口和一個(gè)方法:
首先是java.lang.reflect包里的InvocationHandler接口:
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
我們對(duì)于被代理的類的操作都會(huì)由該接口中的invoke方法實(shí)現(xiàn),其中的參數(shù)的含義分別是:
- proxy:被代理的類的實(shí)例
- method:調(diào)用被代理的類的方法
- args:該方法需要的參數(shù)
使用方法首先是需要實(shí)現(xiàn)該接口,并且我們可以在invoke方法中調(diào)用被代理類的方法并獲得返回值,自然也可以在調(diào)用該方法的前后去做一些額外的事情,從而實(shí)現(xiàn)動(dòng)態(tài)代理,下面的例子會(huì)詳細(xì)寫到。
另外一個(gè)很重要的靜態(tài)方法是java.lang.reflect包中的Proxy類的newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
其中的參數(shù)含義如下:
- loader:被代理的類的類加載器
- interfaces:被代理類的接口數(shù)組
- invocationHandler:就是剛剛介紹的調(diào)用處理器類的對(duì)象實(shí)例
該方法會(huì)返回一個(gè)被修改過的類的實(shí)例,從而可以自由的調(diào)用該實(shí)例的方法。下面是一個(gè)實(shí)際例子。
Fruit接口: public interface Fruit { public void show(); } Apple實(shí)現(xiàn)Fruit接口: public class Apple implements Fruit{ @Override public void show() { System.out.println("<<<); } } 代理類Agent.java: public class DynamicAgent { //實(shí)現(xiàn)InvocationHandler接口,并且可以初始化被代理類的對(duì)象 static class MyHandler implements InvocationHandler { private Object proxy; public MyHandler(Object proxy) { this.proxy = proxy; } //自定義invoke方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(">>>>before invoking"); //真正調(diào)用方法的地方 Object ret = method.invoke(this.proxy, args); System.out.println(">>>>after invoking"); return ret; } } //返回一個(gè)被修改過的對(duì)象 public static Object agent(Class interfaceClazz, Object proxy) { return Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class[]{interfaceClazz}, new MyHandler(proxy)); } } 測試類: public class ReflectTest { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { //注意一定要返回接口,不能返回實(shí)現(xiàn)類否則會(huì)報(bào)錯(cuò) Fruit fruit = (Fruit) DynamicAgent.agent(Fruit.class, new Apple()); fruit.show(); } }
結(jié)果:
可以看到對(duì)于不同的實(shí)現(xiàn)類來說,可以用同一個(gè)動(dòng)態(tài)代理類來進(jìn)行代理,實(shí)現(xiàn)了“一次編寫到處代理”的效果。
但是這種方法有個(gè)缺點(diǎn),就是被代理的類一定要是實(shí)現(xiàn)了某個(gè)接口的,這很大程度限制了本方法的使用場景。下面還有另外一個(gè)使用了CGlib增強(qiáng)庫的方法。
2.3 CGLIB庫的方法
CGlib是一個(gè)字節(jié)碼增強(qiáng)庫,為AOP等提供了底層支持。下面看看它是怎么實(shí)現(xiàn)動(dòng)態(tài)代理的。
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CGlibAgent implements MethodInterceptor { private Object proxy; public Object getInstance(Object proxy) { this.proxy = proxy; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.proxy.getClass()); // 回調(diào)方法 enhancer.setCallback(this); // 創(chuàng)建代理對(duì)象 return enhancer.create(); } //回調(diào)方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println(">>>>before invoking"); //真正調(diào)用 Object ret = methodProxy.invokeSuper(o, objects); System.out.println(">>>>after invoking"); return ret; } public static void main(String[] args) { CGlibAgent cGlibAgent = new CGlibAgent(); Apple apple = (Apple) cGlibAgent.getInstance(new Apple()); apple.show(); } }
三、Spring中的AOP: @AspectJ
3.1 @AspectJ 由來
AspectJ是一個(gè)AOP框架,它能夠?qū)ava代碼進(jìn)行AOP編譯(一般在編譯期進(jìn)行),讓java代碼具有AspectJ的AOP功能(當(dāng)然需要特殊的編譯器),可以這樣說AspectJ是目前實(shí)現(xiàn)AOP框架中最成熟,功能最豐富的語言,更幸運(yùn)的是,AspectJ與java程序完全兼容,幾乎是無縫關(guān)聯(lián),因此對(duì)于有java編程基礎(chǔ)的工程師,上手和使用都非常容易。
其實(shí)AspectJ單獨(dú)就是一門語言,它需要專門的編譯器(ajc編譯器). Spring AOP 與ApectJ的目的一致,都是為了統(tǒng)一處理橫切業(yè)務(wù),但與AspectJ不同的是,Spring AOP并不嘗試提供完整的AOP功能(即使它完全可以實(shí)現(xiàn)),Spring AOP 更注重的是與Spring IOC容器的結(jié)合,并結(jié)合該優(yōu)勢來解決橫切業(yè)務(wù)的問題,因此在AOP的功能完善方面,相對(duì)來說AspectJ具有更大的優(yōu)勢,同時(shí),Spring注意到AspectJ在AOP的實(shí)現(xiàn)方式上依賴于特殊編譯器(ajc編譯器),因此Spring很機(jī)智回避了這點(diǎn),轉(zhuǎn)向采用動(dòng)態(tài)代理技術(shù)的實(shí)現(xiàn)原理來構(gòu)建Spring AOP的內(nèi)部機(jī)制(動(dòng)態(tài)織入),這是與AspectJ(靜態(tài)織入)最根本的區(qū)別。在AspectJ 1.5后,引入@Aspect形式的注解風(fēng)格的開發(fā),Spring也非??斓馗M(jìn)了這種方式,因此Spring 2.0后便使用了與AspectJ一樣的注解。請(qǐng)注意,Spring 只是使用了與 AspectJ 5 一樣的注解,但仍然沒有使用 AspectJ 的編譯器,底層依是動(dòng)態(tài)代理技術(shù)的實(shí)現(xiàn),因此并不依賴于 AspectJ 的編譯器。
所以,Spring AOP雖然是使用了AspectJ那一套注解,其實(shí)實(shí)現(xiàn)AOP的底層是使用了動(dòng)態(tài)代理(JDK或者CGLib)來動(dòng)態(tài)植入。
3.2 舉個(gè)栗子
小狗類,會(huì)說話:
? public class Dog { private String name; public void say(){ System.out.println(name + "在汪汪叫!..."); } public String getName() { return name; } public void setName(String name) { this.name = name; } } 切面類: @Aspect //聲明自己是一個(gè)切面類 public class MyAspect { /** * 前置通知 */ //@Before是增強(qiáng)中的方位 // @Before括號(hào)中的就是切入點(diǎn)了 //before()就是傳說的增強(qiáng)(建言):說白了,就是要干啥事. @Before("execution(* com.zdy..*(..))") public void before(){ System.out.println("前置通知...."); } } ?
這個(gè)類是重點(diǎn),先用@Aspect聲明自己是切面類,然后before()為增強(qiáng),@Before(方位)+切入點(diǎn)可以具體定位到具體某個(gè)類的某個(gè)方法的方位. Spring配置文件:
//開啟AspectJ功能. <aop:aspectj-autoproxy /> <bean id="dog" class="com.zdy.Dog" /> <bean name="myAspect" class="com.zdy.MyAspect"/> 然后Main方法: ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml"); Dog dog =(Dog) ac.getBean("dog"); System.out.println(dog.getClass()); dog.say();
輸出結(jié)果:
class com.zdy.Dog$$EnhancerBySpringCGLIB$$80a9ee5f
前置通知....
null在汪汪叫!...
說白了,就是把切面類丟到容器,開啟一個(gè)AdpectJ的功能,Spring AOP就會(huì)根據(jù)切面類中的(@Before+切入點(diǎn))定位好具體的類的某個(gè)方法(我這里定義的是com.zdy包下的所有類的所有方法),然后把增強(qiáng)before()切入進(jìn)去.
3.3 舉個(gè)Spring Boot中的栗子
這個(gè)栗子很實(shí)用,關(guān)于Aop做切面去統(tǒng)一處理Web請(qǐng)求的日志:
@Aspect @Component public class WebLogAspect { private Logger logger = Logger.getLogger(getClass()); @Pointcut("execution(public * com.didispace.web..*.*(..))") public void webLog(){} @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到請(qǐng)求,記錄請(qǐng)求內(nèi)容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 記錄下請(qǐng)求內(nèi)容 logger.info("URL : " + request.getRequestURL().toString()); logger.info("HTTP_METHOD : " + request.getMethod()); logger.info("IP : " + request.getRemoteAddr()); logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請(qǐng)求,返回內(nèi)容 logger.info("RESPONSE : " + ret); } }
可以看上面的例子,通過 @Pointcut 定義的切入點(diǎn)為 com.didispace.web 包下的所有函數(shù)(對(duì)web層所有請(qǐng)求處理做切入點(diǎn)),然后通過 @Before 實(shí)現(xiàn),對(duì)請(qǐng)求內(nèi)容的日志記錄(本文只是說明過程,可以根據(jù)需要調(diào)整內(nèi)容),最后通過 @AfterReturning 記錄請(qǐng)求返回的對(duì)象。
通過運(yùn)行程序并訪問: //localhost:8080/hello?name=didi ,可以獲得下面的日志輸出
2016-05-19 13:42:13,156 INFO WebLogAspect:41 - URL : http://localhost:8080/hello
2016-05-19 13:42:13,156 INFO WebLogAspect:42 - HTTP_METHOD : http://localhost:8080/hello
2016-05-19 13:42:13,157 INFO WebLogAspect:43 - IP : 0:0:0:0:0:0:0:1
2016-05-19 13:42:13,160 INFO WebLogAspect:44 - CLASS_METHOD : com.didispace.web.HelloController.hello
2016-05-19 13:42:13,160 INFO WebLogAspect:45 - ARGS : [didi]
2016-05-19 13:42:13,170 INFO WebLogAspect:52 - RESPONSE:Hello didi
3.4 Spring AOP支持的幾種AspectJ注解
- 前置通知@Before: 前置通知通過@Before注解進(jìn)行標(biāo)注,并可直接傳入切點(diǎn)表達(dá)式的值,該通知在目標(biāo)函數(shù)執(zhí)行前執(zhí)行,注意JoinPoint,是Spring提供的靜態(tài)變量,通過joinPoint 參數(shù),可以獲取目標(biāo)對(duì)象的信息,如類名稱,方法參數(shù),方法名稱等,該參數(shù)是可選的。
@Before("execution(...)") public void before(JoinPoint joinPoint){ System.out.println("..."); }
- 后置通知@AfterReturning: 通過@AfterReturning注解進(jìn)行標(biāo)注,該函數(shù)在目標(biāo)函數(shù)執(zhí)行完成后執(zhí)行,并可以獲取到目標(biāo)函數(shù)最終的返回值returnVal,當(dāng)目標(biāo)函數(shù)沒有返回值時(shí),returnVal將返回null,必須通過returning = “returnVal”注明參數(shù)的名稱而且必須與通知函數(shù)的參數(shù)名稱相同。請(qǐng)注意,在任何通知中這些參數(shù)都是可選的,需要使用時(shí)直接填寫即可,不需要使用時(shí),可以完成不用聲明出來。
@AfterReturning(value="execution(...)",returning = "returnVal") public void AfterReturning(JoinPoint joinPoint,Object returnVal){ System.out.println("我是后置通知...returnVal+"+returnVal); }
- 異常通知 @AfterThrowing:該通知只有在異常時(shí)才會(huì)被觸發(fā),并由throwing來聲明一個(gè)接收異常信息的變量,同樣異常通知也用于Joinpoint參數(shù),需要時(shí)加上即可.
@AfterThrowing(value="execution(....)",throwing = "e") public void afterThrowable(Throwable e){ System.out.println("出現(xiàn)異常:msg="+e.getMessage()); }
- 最終通知 @After:該通知有點(diǎn)類似于finally代碼塊,只要應(yīng)用了無論什么情況下都會(huì)執(zhí)行.
@After("execution(...)") public void after(JoinPoint joinPoint) { System.out.println("最終通知...."); }
- 環(huán)繞通知 @Around: 環(huán)繞通知既可以在目標(biāo)方法前執(zhí)行也可在目標(biāo)方法之后執(zhí)行,更重要的是環(huán)繞通知可以控制目標(biāo)方法是否指向執(zhí)行,但即使如此,我們應(yīng)該盡量以最簡單的方式滿足需求,在僅需在目標(biāo)方法前執(zhí)行時(shí),應(yīng)該采用前置通知而非環(huán)繞通知。案例代碼如下第一個(gè)參數(shù)必須是ProceedingJoinPoint,通過該對(duì)象的proceed()方法來執(zhí)行目標(biāo)函數(shù),proceed()的返回值就是環(huán)繞通知的返回值。同樣的,ProceedingJoinPoint對(duì)象也是可以獲取目標(biāo)對(duì)象的信息,如類名稱,方法參數(shù),方法名稱等等
@Around("execution(...)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("我是環(huán)繞通知前...."); //執(zhí)行目標(biāo)函數(shù) Object obj= (Object) joinPoint.proceed(); System.out.println("我是環(huán)繞通知后...."); return obj; }
然后說下一直用"…"忽略掉的切入點(diǎn)表達(dá)式,這個(gè)表達(dá)式可以不是exection(…),還有其他的一些,我就不說了,說最常用的execution:
? //scope :方法作用域,如public,private,protect //returnt-type:方法返回值類型 //fully-qualified-class-name:方法所在類的完全限定名稱 //parameters 方法參數(shù) execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters)) <fully-qualified-class-name>.*(parameters) ?
注意這一塊,如果沒有精確到class-name,而是到包名就停止了,要用兩個(gè)"…"來表示包下的任意類:
- execution(* com.zdy…*(…)):com.zdy包下所有類的所有方法.
- execution(* com.zdy.Dog.*(…)): Dog類下的所有方法.
具體詳細(xì)語法,大家如果有需求自行g(shù)oogle了,我最常用的就是這倆了。要么按照包來定位,要么按照具體類來定位.
在使用切入點(diǎn)時(shí),還可以抽出來一個(gè)@Pointcut來供使用:
/** * 使用Pointcut定義切點(diǎn) */ @Pointcut("execution(...)") private void myPointcut(){} /** * 應(yīng)用切入點(diǎn)函數(shù) */ @After(value="myPointcut()") public void afterDemo(){ System.out.println("最終通知...."); }
可以避免重復(fù)的execution在不同的注解里寫很多遍…
3.5 AOP切面的優(yōu)先級(jí)
由于通過AOP實(shí)現(xiàn),程序得到了很好的解耦,但是也會(huì)帶來一些問題,比如:我們可能會(huì)對(duì)Web層做多個(gè)切面,校驗(yàn)用戶,校驗(yàn)頭信息等等,這個(gè)時(shí)候經(jīng)常會(huì)碰到切面的處理順序問題。
所以,我們需要定義每個(gè)切面的優(yōu)先級(jí),我們需要@Order(i)注解來標(biāo)識(shí)切面的優(yōu)先級(jí)。i的值越小,優(yōu)先級(jí)越高。假設(shè)我們還有一個(gè)切面是CheckNameAspect用來校驗(yàn)name必須為derry,我們?yōu)槠湓O(shè)置@Order(10),而上文中WebLogAspect設(shè)置為@Order(5),所以WebLogAspect有更高的優(yōu)先級(jí),這個(gè)時(shí)候執(zhí)行順序是這樣的:
- 在@Before中優(yōu)先執(zhí)行@Order(5)的內(nèi)容,再執(zhí)行@Order(10)的內(nèi)容
- 在@After和@AfterReturning中優(yōu)先執(zhí)行@Order(10)的內(nèi)容,再執(zhí)行@Order(5)的內(nèi)容
所以我們可以這樣子總結(jié):
- 在切入點(diǎn)前的操作,按order的值由小到大執(zhí)行
- 在切入點(diǎn)后的操作,按order的值由大到小執(zhí)行
到此這篇關(guān)于Java中的SpringAOP、代理模式、常用AspectJ注解詳解的文章就介紹到這了,更多相關(guān)AOP、代理模式與AspectJ內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringAop中AspectJ框架的切入點(diǎn)表達(dá)式
- 詳解Spring中的AOP及AspectJ五大通知注解
- 一次"java:程序包org.aspectj.lang不存在"問題解決實(shí)戰(zhàn)記錄
- 關(guān)于Spring的AnnotationAwareAspectJAutoProxyCreator類解析
- 在Spring AOP中代理對(duì)象創(chuàng)建的步驟詳解
- Spring之AOP兩種代理機(jī)制對(duì)比分析(JDK和CGLib動(dòng)態(tài)代理)
- SpringBoot/Spring?AOP默認(rèn)動(dòng)態(tài)代理方式實(shí)例詳解
相關(guān)文章
SpringBoot深入理解之內(nèi)置web容器及配置的總結(jié)
今天小編就為大家分享一篇關(guān)于SpringBoot深入理解之內(nèi)置web容器及配置的總結(jié),小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03Scala可變參數(shù)列表,命名參數(shù)和參數(shù)缺省詳解
這篇文章主要介紹了Scala可變參數(shù)列表,命名參數(shù)和參數(shù)缺省詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06SpringCloud Feign 服務(wù)調(diào)用的實(shí)現(xiàn)
Feign是一個(gè)聲明性web服務(wù)客戶端。本文記錄多個(gè)服務(wù)之間使用Feign調(diào)用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01