自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官:說(shuō)說(shuō)你對(duì)Spring AOP的實(shí)現(xiàn)機(jī)制的理解!

開(kāi)發(fā) 后端
靜態(tài)代理是編譯期實(shí)現(xiàn),動(dòng)態(tài)代理是運(yùn)行期實(shí)現(xiàn),可想而知前者擁有更好的性能。本文主要介紹Spring AOP的兩種代理實(shí)現(xiàn)機(jī)制,JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。

 

AOP(Aspect Orient Programming),一般稱(chēng)為面向切面編程,作為面向?qū)ο蟮囊环N補(bǔ)充,用于處理系統(tǒng)中分布于各個(gè)模塊的橫切關(guān)注點(diǎn),比如事務(wù)管理、日志、緩存等等。AOP實(shí)現(xiàn)的關(guān)鍵在于AOP框架自動(dòng)創(chuàng)建的AOP代理,AOP代理主要分為靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理的代表為AspectJ;而動(dòng)態(tài)代理則以Spring AOP為代表。靜態(tài)代理是編譯期實(shí)現(xiàn),動(dòng)態(tài)代理是運(yùn)行期實(shí)現(xiàn),可想而知前者擁有更好的性能。

本文主要介紹Spring AOP的兩種代理實(shí)現(xiàn)機(jī)制,JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。

靜態(tài)代理是編譯階段生成AOP代理類(lèi),也就是說(shuō)生成的字節(jié)碼就織入了增強(qiáng)后的AOP對(duì)象;動(dòng)態(tài)代理則不會(huì)修改字節(jié)碼,而是在內(nèi)存中臨時(shí)生成一個(gè)AOP對(duì)象,這個(gè)AOP對(duì)象包含了目標(biāo)對(duì)象的全部方法,并且在特定的切點(diǎn)做了增強(qiáng)處理,并回調(diào)原對(duì)象的方法。

Spring AOP中的動(dòng)態(tài)代理主要有兩種方式,JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。JDK動(dòng)態(tài)代理通過(guò)反射來(lái)接收被代理的類(lèi),并且要求被代理的類(lèi)必須實(shí)現(xiàn)一個(gè)接口。JDK動(dòng)態(tài)代理的核心是InvocationHandler接口和Proxy類(lèi)。

如果目標(biāo)類(lèi)沒(méi)有實(shí)現(xiàn)接口,那么Spring AOP會(huì)選擇使用CGLIB來(lái)動(dòng)態(tài)代理目標(biāo)類(lèi)。CGLIB(Code Generation Library),是一個(gè)代碼生成的類(lèi)庫(kù),可以在運(yùn)行時(shí)動(dòng)態(tài)的生成某個(gè)類(lèi)的子類(lèi),注意,CGLIB是通過(guò)繼承的方式做的動(dòng)態(tài)代理,因此如果某個(gè)類(lèi)被標(biāo)記為final,那么它是無(wú)法使用CGLIB做動(dòng)態(tài)代理的,諸如private的方法也是不可以作為切面的。

我們分別通過(guò)實(shí)例來(lái)研究AOP的具體實(shí)現(xiàn)。

直接使用Spring AOP

首先定義需要切入的接口和實(shí)現(xiàn)。為了簡(jiǎn)單起見(jiàn),定義一個(gè)Speakable接口和一個(gè)具體的實(shí)現(xiàn)類(lèi),只有兩個(gè)方法sayHi()和sayBye()。 

  1. public interface Speakable {  
  2.  void sayHi();  
  3.  void sayBye();  
  4.  
  5. @Service  
  6. public class PersonSpring implements Speakable {  
  7.  @Override  
  8.  public void sayHi() {  
  9.  try {  
  10.  Thread.currentThread().sleep(30);  
  11.  } catch (Exception e) {  
  12.  throw new RuntimeException(e);  
  13.  }  
  14.  System.out.println("Hi!!");  
  15.  }  
  16.  @Override  
  17.  public void sayBye() {  
  18.  try {  
  19.  Thread.currentThread().sleep(10);  
  20.  } catch (Exception e) {  
  21.  throw new RuntimeException(e);  
  22.  }  
  23.  System.out.println("Bye!!");  
  24.  }  

接下來(lái)我們希望實(shí)現(xiàn)一個(gè)記錄sayHi()和sayBye()執(zhí)行時(shí)間的功能。

定義一個(gè)MethodMonitor類(lèi)用來(lái)記錄Method執(zhí)行時(shí)間 

  1. public class MethodMonitor {  
  2.  private long start;  
  3.  private String method;  
  4.  public MethodMonitor(String method) {  
  5.  this.method = method;  
  6.  System.out.println("begin monitor..");  
  7.  this.start = System.currentTimeMillis();  
  8.  }  
  9.  public void log() {  
  10.  long elapsedTime = System.currentTimeMillis() - start;  
  11.  System.out.println("end monitor..");  
  12.  System.out.println("Method: " + method + ", execution time: " + elapsedTime + " milliseconds.");  
  13.  }  

光有這個(gè)類(lèi)還是不夠的,希望有個(gè)靜態(tài)方法用起來(lái)更順手,像這樣 

  1. MonitorSession.begin();  
  2. doWork();  
  3. MonitorSession.end(); 

說(shuō)干就干,定義一個(gè)MonitorSession 

  1. public class MonitorSession {  
  2.  private static ThreadLocal<MethodMonitor> monitorThreadLocal = new ThreadLocal<>();  
  3.  public static void begin(String method) {  
  4.  MethodMonitor logger = new MethodMonitor(method);  
  5.  monitorThreadLocal.set(logger);  
  6.  }  
  7.  public static void end() {  
  8.  MethodMonitor logger = monitorThreadLocal.get();  
  9.  logger.log();  
  10.  }  

萬(wàn)事具備,接下來(lái)只需要我們做好切面的編碼, 

  1. @Aspect  
  2. @Component  
  3. public class MonitorAdvice {  
  4.  @Pointcut("execution (* com.deanwangpro.aop.service.Speakable.*(..))")  
  5.  public void pointcut() {  
  6.  }  
  7.  @Around("pointcut()")  
  8.  public void around(ProceedingJoinPoint pjp) throws Throwable {  
  9.  MonitorSession.begin(pjp.getSignature().getName());  
  10.  pjp.proceed();  
  11.  MonitorSession.end();  
  12.  }  

如何使用?我用了spring boot,寫(xiě)一個(gè)啟動(dòng)函數(shù)吧。 

  1. @SpringBootApplication  
  2. public class Application {  
  3.  @Autowired  
  4.  private Speakable personSpring;  
  5.  public static void main(String[] args) {  
  6.  SpringApplication.run(Application.class, args);  
  7.  }  
  8.  @Bean  
  9.  public CommandLineRunner commandLineRunner(ApplicationContext ctx) {  
  10.  return args -> {  
  11.  // spring aop  
  12.  System.out.println("******** spring aop ******** ");  
  13.  personSpring.sayHi();  
  14.  personSpring.sayBye();  
  15.  System.exit(0);  
  16.  };  
  17.  }  

運(yùn)行后輸出:   

  1. jdk dynamic proxy begin monitor..Hi!!end monitor..Method: sayHi, execution time: 32 milliseconds.begin monitor..Bye!!end monitor..Method: sayBye, execution time: 22 milliseconds. 

JDK動(dòng)態(tài)代理

剛剛的例子其實(shí)內(nèi)部實(shí)現(xiàn)機(jī)制就是JDK動(dòng)態(tài)代理,因?yàn)镻erson實(shí)現(xiàn)了一個(gè)接口。

為了不和***個(gè)例子沖突,我們?cè)俣x一個(gè)Person來(lái)實(shí)現(xiàn)Speakable, 這個(gè)實(shí)現(xiàn)是不帶Spring Annotation的,所以他不會(huì)被Spring托管。 

  1. public class PersonImpl implements Speakable {  
  2.  @Override  
  3.  public void sayHi() {  
  4.  try {  
  5.  Thread.currentThread().sleep(30);  
  6.  } catch (Exception e) {  
  7.  throw new RuntimeException(e);  
  8.  }  
  9.  System.out.println("Hi!!");  
  10.  }  
  11.  @Override  
  12.  public void sayBye() {  
  13.  try {  
  14.  Thread.currentThread().sleep(10);  
  15.  } catch (Exception e) {  
  16.  throw new RuntimeException(e);  
  17.  }  
  18.  System.out.println("Bye!!");  
  19.  }  

重頭戲來(lái)了,我們需要利用InvocationHandler實(shí)現(xiàn)一個(gè)代理,讓它去包含Person這個(gè)對(duì)象。那么再運(yùn)行期實(shí)際上是執(zhí)行這個(gè)代理的方法,然后代理再去執(zhí)行真正的方法。所以我們得以在執(zhí)行真正方法的前后做一些手腳。JDK動(dòng)態(tài)代理是利用反射實(shí)現(xiàn),直接看代碼。 

  1. public class DynamicProxy implements InvocationHandler {  
  2.  private Object target;  
  3.  public DynamicProxy(Object object) {  
  4.  this.target = object 
  5.  }  
  6.  @Override  
  7.  public Object invoke(Object arg0, Method arg1, Object[] arg2)  
  8.  throws Throwable {  
  9.  MonitorSession.begin(arg1.getName());  
  10.  Object obj = arg1.invoke(target, arg2);  
  11.  MonitorSession.end();  
  12.  return obj;  
  13.  }  
  14.  @SuppressWarnings("unchecked")  
  15.  public <T> T getProxy() {  
  16.  return (T) Proxy.newProxyInstance(  
  17.  target.getClass().getClassLoader(),  
  18.  target.getClass().getInterfaces(),  
  19.  this  
  20.  );  
  21.  }  

通過(guò)getProxy可以得到這個(gè)代理對(duì)象,invoke就是具體的執(zhí)行方法,可以看到我們?cè)趫?zhí)行每個(gè)真正的方法前后都加了Monitor。

我實(shí)現(xiàn)了一個(gè)工廠類(lèi)來(lái)獲取Person代理對(duì)象 

  1. public class PersonProxyFactory {  
  2.  public static Speakable newJdkProxy() {  
  3.  // 代理PersonImpl  
  4.  DynamicProxy dynamicProxy = new DynamicProxy(new PersonImpl());  
  5.  Speakable proxy = dynamicProxy.getProxy();  
  6.  return proxy;  
  7.  }  

具體使用 

  1. // jdk dynamic proxy  
  2. System.out.println("******** jdk dynamic proxy ******** ");  
  3. Speakable jdkProxy = PersonProxyFactory.newJdkProxy();  
  4. jdkProxy.sayHi();  
  5. jdkProxy.sayBye(); 

輸出結(jié)果: 

  1. ******** jdk dynamic proxy ********   
  2. begin monitor..  
  3. Hi!!  
  4. end monitor..  
  5. Method: sayHi, execution time: 32 milliseconds.  
  6. begin monitor..  
  7. Bye!!  
  8. end monitor..  
  9. Method: sayBye, execution time: 22 milliseconds. 

CGLib動(dòng)態(tài)代理

我們?cè)傩陆ㄒ粋€(gè)Person來(lái),這次不實(shí)現(xiàn)任何接口。 

  1. public class Person {  
  2.  public void sayHi() {  
  3.  try {  
  4.  Thread.currentThread().sleep(30);  
  5.  } catch (Exception e) {  
  6.  throw new RuntimeException(e);  
  7.  }  
  8.  System.out.println("Hi!!");  
  9.  }  
  10.  public void sayBye() {  
  11.  try {  
  12.  Thread.currentThread().sleep(10);  
  13.  } catch (Exception e) {  
  14.  throw new RuntimeException(e);  
  15.  }  
  16.  System.out.println("Bye!!");  
  17.  }  

如果Spring識(shí)別到所代理的類(lèi)沒(méi)有實(shí)現(xiàn)Interface,那么就會(huì)使用CGLib來(lái)創(chuàng)建動(dòng)態(tài)代理,原理實(shí)際上成為所代理類(lèi)的子類(lèi)。 

  1. public class CGLibProxy implements MethodInterceptor {  
  2.  private static CGLibProxy instance = new CGLibProxy();  
  3.  private CGLibProxy() {  
  4.  }  
  5.  public static CGLibProxy getInstance() {  
  6.  return instance;  
  7.  }  
  8.  private Enhancer enhancer = new Enhancer();  
  9.  @SuppressWarnings("unchecked")  
  10.  public <T> T getProxy(Class<T> clazz) {  
  11.  enhancer.setSuperclass(clazz);  
  12.  enhancer.setCallback(this);  
  13.  return (T) enhancer.create();  
  14.  }  
  15.  @Override  
  16.  public Object intercept(Object arg0, Method arg1, Object[] arg2,  
  17.  MethodProxy arg3) throws Throwable {  
  18.  MonitorSession.begin(arg1.getName());  
  19.  Object obj = arg3.invokeSuper(arg0, arg2);  
  20.  MonitorSession.end();  
  21.  return obj;  
  22.  }  

類(lèi)似的通過(guò)getProxy可以得到這個(gè)代理對(duì)象,intercept就是具體的執(zhí)行方法,可以看到我們?cè)趫?zhí)行每個(gè)真正的方法前后都加了Monitor。

在工廠類(lèi)中增加獲得Person代理類(lèi)的方法, 

  1. public static Person newCglibProxy() {  
  2.  CGLibProxy cglibProxy = CGLibProxy.getInstance();  
  3.  Person proxy = cglibProxy.getProxy(Person.class);  
  4.  return proxy;  

具體使用 

  1. // cglib dynamic proxy  
  2. System.out.println("******** cglib proxy ******** ");  
  3. Person cglibProxy = PersonProxyFactory.newCglibProxy();  
  4. cglibProxy.sayHi();  
  5. cglibProxy.sayBye(); 

輸出結(jié)果: 

  1. begin monitor..Hi!!end monitor..Method: sayHi, execution time: 53 milliseconds.begin monitor..Bye!!end monitor..Method: sayBye, execution time: 14 milliseconds. 

小結(jié)

對(duì)比JDK動(dòng)態(tài)代理和CGLib代理,在實(shí)際使用中發(fā)現(xiàn)CGLib在創(chuàng)建代理對(duì)象時(shí)所花費(fèi)的時(shí)間卻比JDK動(dòng)態(tài)代理要長(zhǎng),實(shí)測(cè)數(shù)據(jù) 

  1. Method: newJdkProxy, execution time: 5 milliseconds.  
  2. Method: newCglibProxy, execution time: 18 milliseconds. 

所以CGLib更適合代理不需要頻繁實(shí)例化的類(lèi)。

在具體方法執(zhí)行效率方面,理應(yīng)是不通過(guò)反射的CGlib更快一些,然后測(cè)試結(jié)果并非如此,還需要高手指教。   

  1. JDKMethod: sayHi, execution time: 32 milliseconds.CGLibMethod: sayHi, execution time: 53 milliseconds.  
責(zé)任編輯:龐桂玉 來(lái)源: 今日頭條
相關(guān)推薦

2021-11-25 10:18:42

RESTfulJava互聯(lián)網(wǎng)

2021-08-09 07:47:40

Git面試版本

2020-12-01 08:47:36

Java異常開(kāi)發(fā)

2020-06-12 15:50:56

options前端服務(wù)器

2021-11-02 22:04:58

模式

2021-09-28 07:12:09

測(cè)試路徑

2021-09-29 07:24:20

場(chǎng)景數(shù)據(jù)

2021-09-16 07:52:18

算法應(yīng)用場(chǎng)景

2021-06-30 07:19:36

React事件機(jī)制

2021-10-08 09:59:32

冒泡排序場(chǎng)景

2021-10-13 18:01:33

快速排序場(chǎng)景

2021-10-09 10:25:41

排序應(yīng)用場(chǎng)景

2021-11-09 08:51:13

模式命令面試

2020-12-04 06:27:04

序列化面試官Java

2021-11-05 07:47:56

代理模式對(duì)象

2021-11-03 14:10:28

工廠模式場(chǎng)景

2021-11-10 07:47:49

組合模式場(chǎng)景

2022-02-21 17:24:18

序列化對(duì)象存儲(chǔ)

2021-08-16 08:33:26

git

2021-09-26 10:57:16

集合操作場(chǎng)景
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)