Spring AOP高級——源碼實現(3)AopProxy代理對象之JDK動態代理的創建過程

来源:http://www.cnblogs.com/yulinfeng/archive/2017/11/23/7876386.html
-Advertisement-
Play Games

spring-aop-4.3.7.RELEASE 在《Spring AOP高級——源碼實現(1)動態代理技術》中介紹了兩種動態代理技術,當然在Spring AOP中代理對象的生成也是運用的這兩種技術。本文將介紹Spring AOP如何通過JDK動態代理的方式創建代理對象。 JDK動態代理以及CGLI ...


spring-aop-4.3.7.RELEASE 

  在《Spring AOP高級——源碼實現(1)動態代理技術》中介紹了兩種動態代理技術,當然在Spring AOP中代理對象的生成也是運用的這兩種技術。本文將介紹Spring AOP如何通過JDK動態代理的方式創建代理對象。

  JDK動態代理以及CGLIB代理這兩種生成代理對象的方式在Spring AOP中分別對應兩個類:JdkDynamicAopProxy和ObjenesisCglibAopProxy,而AopProxy是這兩個類的父介面。

  AopProxy介面中定義了兩個基本方法:

 1 public interface AopProxy {
 2    /**
 3     *使用預設的類載入器生成代理對象,預設的類載入器通常是當前線程
 4     *的上下文類載入器,可通過Thread#getContextClassLoader()獲得
 5     */
 6    Object getProxy();
 7    /**
 8     * 使用指定的類載入器創建代理對象,通常用於比較低級別的代理對象
 9     * 創建,至於什麼時候用這個暫時先放一放
10     */
11    Object getProxy(ClassLoader classLoader);
12 }

  接著來看它的實現——JdkDynamicAopProxy。

 1 //①首先查看JdkDynamicAopProxy類的成員變數
 2 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {    //JDK動態代理需要實現 InvocationHandler類
 3 
 4    private static final long serialVersionUID = 5531744639992436476L;
 5 
 6    private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);
 7 
 8    /** 代理類的相關配置,這個類繼承自ProxyConfig,都是代理類的相關配置*/
 9    private final AdvisedSupport advised;
10 
11    /**
12     * 用於判斷目標對象實現的介面是否定義了equals方法
13     */
14    private boolean equalsDefined;
15 
16    /**
17     * 用於判斷目標對象實現的介面是否定義了hashCode方法
18     */
19    private boolean hashCodeDefined;

  類中有兩個成員變數需要額外註意一下,一個是第14行的equalsDefined變數和第19行的hashCodeDefined變數。

  討論它們之前需要首先明確一點,通常情況下,Spring AOP代理對象不會對equals和hashCode方法增強,註意這是在通常情況下,那什麼是“通常情況”,什麼又是“不通常情況”呢?

  如果目標對象直接重寫Object對象的equals或hashCode方法,此時Spring AOP則不會對它增強,equalsDefined=false或hashCodeDefined=false;如果目標對象實現的介面定義了equals或hashCode方法,此時Spring AOP則會對它增強,equalsDefined=true或hashCodeDefined=true。所以“通常情況”就是我們並不會在介面定義equals或hashCode方法,“不通常情況”就是在有的特殊情況下在介面定義equals或hashCode方法。再換句話說,如果我們需要Spring AOP增強equals或hashCode方法則必須要在其目標對象的實現介面定義equals或hashCode方法。

1 //②再來看看JdkDynamicAopProxy類的構造方法
2 public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {    //只有一個帶AdvisedSupport類型的構造方法,這個類型上面提到過是生成代理類的相關配置,必須不能為空,否則將拋出參數異常的錯誤
3    Assert.notNull(config, "AdvisedSupport must not be null");
4    if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {    //AdvisedSupport配置類中需要定義通知器和目標源。
5       throw new AopConfigException("No advisors and no TargetSource specified");
6    }
7    this.advised = config;    //賦值給成員變數
8 }

  構造方法中對於參數的校驗有個比較特殊地方,構造方法不僅需要判斷參數不能為空,而且要判斷參數中的兩個變數——advisors通知器和targetSource目標源。

  通知器在《Spring AOP高級——源碼實現(2)Spring AOP中通知器(Advisor)與切麵(Aspect)》中講解過它是一個特殊的切麵,而targetSource目標則是為了獲取target目標對象,這兩個都是實現AOP的重要組成部分必然不能為空。

 1 //③接下來是生成代理對象的getProxy方法
 2 @Override
 3 public Object getProxy() {
 4    return getProxy(ClassUtils.getDefaultClassLoader());    //沒有指定ClassLoader則通過Thread.currentThread.getContextClassLoader()獲取當前線程上下文的類載入器。
 5 }
 6 
 7 @Override
 8 public Object getProxy(ClassLoader classLoader) {
 9    if (logger.isDebugEnabled()) {
10       logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
11    }
12    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);    //獲取代理對象需要實現的完整介面
13    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);    //這裡就是上面提到過的判斷的目標對象實現的介面是否定義了equals或hashCode方法,具體原因不再展開
14    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);    //通過JDK生成代理對象。第一個ClassLoader代表創建類的類載入器,第二個表示需要被代理的目標對象的介面,第三個參數InvocationHandler叫做調用處理器,在這裡它就是對象本身,調用的代理對象方法實際就是調用InvocationHandler介面中的invoke方法。
15 }

   第12行調用了AopProxyUtils.completeProxiedInterfaces(this.advised, true)方法,傳入了Spring AOP代理對象配置對象,第二個參數表示是否暴露DecoratingProxy介面,如果設置為true則代理對象會實現DecoratingProxy介面,這個方法是在Spring4.3後新增的方法。來看看這個方法的實現。

 1 static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
 2     Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();    //獲取目標對象實現的介面
 3//省略的代碼是通過AdviceSupport沒有獲取到目標對象的實現介面時,則通過直接通過target目標對象來獲取
 4     boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);//是否新增SpringProxy,在AdvisedSupport#isInterfaceProxied方法中會判斷傳入的介面是否已經由目標對象實現。此處傳入SpringProxy.class判斷目標對象是否已經實現該介面,如果沒有實現則在代理對象中需要新增SpringProxy,如果實現了則不必新增。
 5     boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);    //是否新增Adviced介面,註意不是Advice通知介面。ProxyConfig#isOpaque方法用於返回由這個配置創建的代理對象是否應該避免被強制轉換為Advised類型。還有一個條件和上面的方法一樣,同理,傳入Advised.class判斷目標對象是否已經實現該介面,如果沒有實現則在代理對象中需要新增Advised,如果實現了則不必新增。
 6     boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));    //是否新增DecoratingProxy介面,同樣的判斷條件有兩個,第一個參數decoratingProxy,在調用completeProxiedInterfaces方法時傳入的是true,第二個判斷條件和上面一樣判斷被代理的目標對象是否已經實現了DecoratingProxy介面。通常情況下這個介面也會被加入到代理對象中,這是Spring4.3新增的。
 7     int nonUserIfcCount = 0;
 8     if (addSpringProxy) {
 9         nonUserIfcCount++;
10     }
11     if (addAdvised) {
12         nonUserIfcCount++;
13     }
14     if (addDecoratingProxy) {
15         nonUserIfcCount++;
16     }
17     Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];    //代理類的介面一共是目標對象的介面+上面三個介面SpringProxy、Advised、DecoratingProxy
18     System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);    //將目標對象的介面拷貝,這個方法在《System.arraycopy(src, srcPos, dest, destPos, length) 與 Arrays.copyOf(original, newLength)區別》有介紹
19 //下麵就是講那三個介面加入到specifiedInterfaces數組中並返回
20     int index = specifiedInterfaces.length;
21     if (addSpringProxy) {
22         proxiedInterfaces[index] = SpringProxy.class;             
23         index++;
24     }
25     if (addAdvised) {
26         proxiedInterfaces[index] = Advised.class;
27         index++;
28     }
29     if (addDecoratingProxy) {
30         proxiedInterfaces[index] = DecoratingProxy.class;
31     }
32     return proxiedInterfaces;    
33 }

   生成代理對象後,下一步當然就是調用代理方法,當然這就包括了調用AOP的增強方法以及目標對象的方法。  

  JDK動態代理生成的代理對象需要實現InvocationHandler介面和invoke方法,這個invoke方法就是JDK代理對象進行攔截的回調入口。 JdkDynamicAopProxy就實現了InvocationHandler介面,這個介面只有一個方法invoke。

public Object invoke(Object proxy, Method method, Object[] args)

  這個方法有三個參數:

  proxy:指的是我們所代理的那個真實的對象;

  method:指的是我們所代理的那個真實對象的某個方法的Method對象;

  args:指的是調用那個真實對象方法的參數。

  接下來一步一步查看JdkDynamicAopProxy是如何實現invoke方法的。

 1 //⑤代理對象的回調方法
 2 @Override
 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 4     MethodInvocation invocation;
 5     Object oldProxy = null;
 6     boolean setProxyContext = false;
 7 
 8     TargetSource targetSource = this.advised.targetSource;
 9     Class<?> targetClass = null;
10     Object target = null;
11 
12     try {
13         if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
14             // 這裡就是之前提到過的,“通常情況”Spring AOP不會對equals方法進行攔截增強,所以這裡判斷如果目標對象沒有定義equals方法的話,就會直接調用而不會增強
15             return equals(args[0]);
16         }
17         else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
18             //同上
19             return hashCode();
20         }
21         else if (method.getDeclaringClass() == DecoratingProxy.class) {
22             // 這裡是上面的疑點,也是Spring4.3新出現的特性
23             return AopProxyUtils.ultimateTargetClass(this.advised);
24         }
25         else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
26                 method.getDeclaringClass().isAssignableFrom(Advised.class)) {
27             //這個地方就有點意思了,Spring AOP不會增強直接實現Advised介面的目標對象,再重覆一次,也就是說如果目標對象實現的Advised介面,則不會對其應用切麵進行方法的增強。
28             return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);  //這個方法是一個對Java通過反射調用方法的封裝  
29         }
30         Object retVal;    //方法的返回值
31         if (this.advised.exposeProxy) {    //是否暴露代理對象,預設false可配置為true,如果暴露就意味著允許線上程內共用代理對象,註意這是線上程內,也就是說同一線程的任意地方都能通過AopContext獲取該代理對象,這應該算是比較高級一點的用法了。
32             oldProxy = AopContext.setCurrentProxy(proxy);
33             setProxyContext = true;
34         }
35         target = targetSource.getTarget();  //通過目標源獲取目標對象
36         if (target != null) {
37             targetClass = target.getClass();    //獲取目標對象Class對象
38         }
39         //...暫略  
40 }

  上面可以說是對方法調用的一些預處理,有幾個重要的地方:

  1. 第13-29行,對一些情況的特殊判斷,主要是不對目標對象應用切麵;
  2. 第31行,判斷是否暴露代理對象,預設false不暴露,如果暴露則表示線上程內的任意位置都能通過AopContext獲取代理對象;
  3. 第35行,通過目標源獲取目標對象。

  下麵可以說是重中之重,也就是具體是如何對目標對象應用切麵對其方法進行增強的。

 1 //⑥獲取攔截器鏈,並調用增強方法及目標對象的方法
 2 @Override
 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 4     //...
 5     List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);    //這個方法獲取攔截器鏈。
 6     if (chain.isEmpty()) {    //攔截器鏈如果為空的話就直接調用目標對象的方法。.
 7        Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
 8        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);    //直接調用目標對象的方法。
 9     }
10     else {    //通過ReflectiveMethodInvocation.proceed調用攔截器中的方法和目標對象方法
11         invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);    //ReflectiveMethodInvocation對象完成對AOP功能實現的封裝    
12         retVal = invocation.proceed();    //獲取返回值
13     }
14     Class<?> returnType = method.getReturnType();    //獲取返回值類型
15     if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
16     // 一些列的判斷條件,如果返回值不為空,且為目標對象的話,就直接將目標對象賦值給retVal
17     retVal = proxy;
18     }
19     else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
20     throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);    //沒有返回值
21     }
22     return retVal;
23 }
24 //...暫略

   這段代碼有3個比較關鍵的地方:

  1. 第5行,獲取攔截器鏈,相當於是獲取潛在的增強方法,只是潛在,後續還有匹配的判斷;
  2. 第8行,沒有獲取到攔截器鏈,此時相當於直接調用目標對象的方法,AopUtils.invokeJoinpointUsingReflection方法實際是對JDK反射調用方法的一個封裝;
  3. 第12行,這裡就是進入攔截器鏈中增強方法和目標對象的調用的地方,關鍵。

  接下來就是進入ReflectiveMethodInvocation.proceed方法,來探討下Spring AOP是如何對目標對象調用方法進行增強以及調用的。

 1 //⑦連接器鏈的調用,ReflectiveMethodInvocation#proceed,這是一個遞歸方法,退出條件就是調用完了攔截鏈中的所有攔截器方法後,再調用目標對象的方法。
 2 這個方法的邏輯在於通過攔截器鏈,逐個獲取其中的攔截器,再通過匹配判斷,判斷是否適用,如果適用則取出攔截器中的通知器並通過通知器的invoke方法調用,如果不適用則遞歸調用。
 3 @Override
 4 public Object proceed() throws Throwable {
 5       if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {    //調用完了所有的攔截鏈中攔截器的增強方法,直接調用目標對象的方法並退出
 6       return invokeJoinpoint();    //這個方法就是調用AopUtils.invokeJoinpointUsingReflection,上面提到過。
 7    }
 8 
 9    Object interceptorOrInterceptionAdvice =
10      this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);    //從攔截器鏈中獲取攔截器
11    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {    //這裡進行動態匹配
12       InterceptorAndDynamicMethodMatcher dm =
13             (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
14       if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {    //這裡如果和定義的切點匹配,那麼這個通知就會得到執行
15          return dm.interceptor.invoke(this);
16       }
17       else {
18          return proceed();    //不適用遞歸繼續獲取攔截器進行匹配、判斷、調用
19       }
20    }
21    else {        //這裡判斷出這個攔截器是一個MethodInterceptor則直接調用
22       return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);   //動態匹配失敗則直接調用
23    }
24 }

  這裡就是整個調用過程,同樣有一個大的重點:

  第11行,這裡是進行動態匹配,什麼是動態匹配呢?我們給出如下的示例,按照xml配置一個通知器而不是切麵。

 1 <!--定義通知器bean,需要實現Advice介面-->
 2 <bean id="testAdvisor" class="com.springdemo.aop.MyAdvice"/>
 3 <!--目標對象-->
 4 <bean id="testPoint" class="com.springdemo.aop.TestPoint"/>
 5 <!--advisor通知器-->
 6 <bean id="testAop" class="org.springframework.aop.framework.ProxyFactoryBean">
 7     <!--目標對象實現的介面-->
 8     <property name="proxyInterfaces">
 9         <value>com.springdemo.aop.Test</value>
10     </property>
11     <!--攔截器,也就是上面定義的通知器beanId-->
12     <property name="interceptorNames">
13         <list>
14             <value>testAdvisor</value>
15         </list>
16     </property>
17     <!--目標對象,也就是上面定義的目標對象beanId-->
18     <property name="targetName">
19         <value>testPoint</value>
20     </property>
21 </bean>

  上面的註釋已經比較清楚了,如果我們按照這樣的方式來實現一個目標對象的方法增強,那麼此時,在調用代理對象的方法時,也就是在執行上面ReflectiveMethodInvocation.proceed方法時是不會進行動態匹配的,因為我們在定義一個advisor通知器也就是上面的Myadvice類的時候是一定會實現Advice介面,如果我們定義的是MethodBeforeAdvice那麼此時就已經確定一定會是前置通知。所以它一定會進入else那個分支去。

  如果,我們不是通過定義advisor通知器的方式,而是直接定義一個切麵,那麼,在我們定義切麵這個類是是不需要實現任何介面的,其中的任意方法都可以作為前置或者後置通知,這取決於你的xml配置,例如下麵的配置:

<aop:config>
    <aop:aspect ref="aspectTest">
        <aop:pointcut id="test" expression="execution(* com.demo.TestPoint.test())"/>
        <aop:before method="doBefore" pointcut-ref="test"/>
        <aop:after-returning method="doAfter" pointcut-ref="test"/>
    </aop:aspect>
</aop:config>

  關於這個地方的理解,建議多寫幾個demo通過打斷點的方式反覆咀嚼。下一篇將會介紹Spring AOP是如何通過CGLib生成代理對象的,本文還有很多很多不足之處,希望有看到的人懂的不懂的都能指出來,非常非常感謝。

 

 

這是一個能給程式員加buff的公眾號 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 關於web變數配置問題 webservice裡面介面完成時考慮到介面可能用在不同的伺服器,不同的資料庫所以將鏈接地址修改成變數,在webconfig裡面去修改它更加的容易方便 假始根據在同一個伺服器裡面,訪問不同的資料庫裡面的數據首先可以將資料庫名申明成為一個變數,在web裡面調用介面時,可以自由填 ...
  • 在看過一篇文章 WPF自定義控制項之列表滑動特效 PowerListBox http://www.cnblogs.com/ShenNan/p/4993374.html#3619585 實現了滑動的特效(就是動畫)之後 ,覺得很有趣 也想在 UWP裡面實現。最好效果如下 接下來就說說是怎麼實現的吧: 一 ...
  • 代碼如下: //複製文件 private void btnCopy_Click(object sender, EventArgs e) { try { if (!File.Exists(this.txtFileName.Text)) { MessageBox.Show("文件不存在"); } els ...
  • Orchard Core 是Orchard CMS的ASP.NET Core版本。 Orchard Core是全新一代的ASP.NET Core CMS。 官方文檔介紹:http://orchardcore.readthedocs.io/en/latest/GitHub: https://githu ...
  • 【返回導航】 在簡單瞭解了Orleans 之後我們可以通過幾個例子去加深印象 一、快速入門示例 這個例子也是跟著《Microsoft Orleans 之 入門指南》(https://www.cnblogs.com/endv/p/6147976.html)這篇文章做的 上篇筆記中的連個例子說明瞭開發的 ...
  • 1、介紹 FSLIB.NETWORK 是一款開源HTTP的高性能高易用性網路庫,是對HttpWebRequest/HttpWebResponse的包裝,目的是為了用起來更簡單明瞭。設計的時候就為了提供更高的可用性和擴展性。每天由它發現的請求數過億。 源碼:https://github.com/icc ...
  • 問題 1072: 汽水瓶 時間限制: 1Sec 記憶體限制: 128MB 題目描述 有這樣一道智力題:“某商店規定:三個空汽水瓶可以換一瓶汽水。小張手上有十個空汽水瓶,她最多可以換多少瓶汽水喝?”答案是5瓶, 方法如下:先用9個空瓶子換3瓶汽水,喝掉3瓶滿的,喝完以後4個空瓶子,用3個再換一瓶,喝掉這 ...
  • 抽象類是由abstract修飾的類,定義方式如public abstract class A{...}。 介面由interface修飾,定義方式如public interface B{...}。 抽象類與介面的具體特性見下文。 抽象類: (1).抽象類中由abstract修飾的方法稱為抽象方法,抽象 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...