java動態代理實現與原理詳細分析

来源:https://www.cnblogs.com/javaerlearn/archive/2018/12/24/10166557.html
-Advertisement-
Play Games

轉載:https://www.cnblogs.com/gonjan-blog/p/6685611.html 關於Java中的動態代理,我們首先需要瞭解的是一種常用的設計模式--代理模式,而對於代理,根據創建代理類的時間點,又可以分為靜態代理和動態代理。 一、代理模式 代理模式是常用的java設計模式 ...


 

轉載:https://www.cnblogs.com/gonjan-blog/p/6685611.html

關於Java中的動態代理,我們首先需要瞭解的是一種常用的設計模式--代理模式,而對於代理,根據創建代理類的時間點,又可以分為靜態代理和動態代理。

 一、代理模式

   代理模式是常用的java設計模式,他的特征是代理類與委托類有同樣的介面,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事後處理消息等。代理類與委托類之間通常會存在關聯關係,一個代理類的對象與一個委托類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。簡單的說就是,我們在訪問實際對象時,是通過代理對象來訪問的,代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。在後面我會

解釋這種間接性帶來的好處。代理模式結構圖(圖片來自《大話設計模式》):

                                                          

二、靜態代理

    1、靜態代理

靜態代理:由程式員創建或特定工具自動生成源代碼,也就是在編譯時就已經將介面,被代理類,代理類等確定下來。在程式運行之前,代理類的.class文件就已經生成。

    2、靜態代理簡單實現

  根據上面代理模式的類圖,來寫一個簡單的靜態代理的例子。我這兒舉一個比較粗糙的例子,假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。這裡,班長就是代理學生上交班費,

班長就是學生的代理。

    首先,我們創建一個Person介面。這個介面就是學生(被代理類),和班長(代理類)的公共介面,他們都有上交班費的行為。這樣,學生上交班費就可以讓班長來代理執行。

複製代碼
/**
 * 創建Person介面
 * @author Gonjan
 */
public interface Person {
    //上交班費
    void giveMoney();
}
複製代碼

Student類實現Person介面。Student可以具體實施上交班費的動作。

複製代碼
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    
    @Override
    public void giveMoney() {
       System.out.println(name + "上交班費50元");
    }
}
複製代碼

StudentsProxy類,這個類也實現了Person介面,但是還另外持有一個學生類對象,由於實現了Peson介面,同時持有一個學生對象,那麼他可以代理學生類對象執行上交班費(執行giveMoney()方法)行為。

複製代碼
/**
 * 學生代理類,也實現了Person介面,保存一個學生實體,這樣既可以代理學生產生行為
 * @author Gonjan
 *
 */
public class StudentsProxy implements Person{
    //被代理的學生
    Student stu;
    
    public StudentsProxy(Person stu) {
        // 只代理學生對象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }
    
    //代理上交班費,調用被代理學生的上交班費行為
    public void giveMoney() {
        stu.giveMoney();
    }
}
複製代碼

下麵測試一下,看如何使用代理模式:

複製代碼
public class StaticProxyTest {
    public static void main(String[] args) {
        //被代理的學生張三,他的班費上交有代理對象monitor(班長)完成
        Person zhangsan = new Student("張三");
        
        //生成代理對象,並將張三傳給代理對象
        Person monitor = new StudentsProxy(zhangsan);
        
        //班長代理上交班費
        monitor.giveMoney();
    }
}
複製代碼

運行結果:

這裡並沒有直接通過張三(被代理對象)來執行上交班費的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。

代理模式最主要的就是有一個公共介面(Person),一個具體的類(Student),一個代理類(StudentsProxy),代理類持有具體類的實例,代為執行具體類實例方法。上面說到,代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。這裡的間接性就是指不直接調用實際對象的方法,那麼我們在代理過程中就可以加上一些其他用途。就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,通過代理模式很輕鬆就能辦到:

複製代碼
public class StudentsProxy implements Person{
    //被代理的學生
    Student stu;
    
    public StudentsProxy(Person stu) {
        // 只代理學生對象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }
    
    //代理上交班費,調用被代理學生的上交班費行為
    public void giveMoney() {
        System.out.println("張三最近學習有進步!");
        stu.giveMoney();
    }
}
複製代碼

運行結果:

可以看到,只需要在代理類中幫張三上交班費之前,執行其他操作就可以了。這種操作,也是使用代理模式的一個很大的優點。最直白的就是在Spring中的面向切麵編程(AOP),我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。

三、動態代理

     1.動態代理

    代理類在程式運行時創建的代理方式被成為動態代理。 我們上面靜態代理的例子中,代理類(studentProxy)是自己定義好的,在程式運行之前就已經編譯完成。然而動態代理,代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。 比如說,想要在每個代理的方法前都加上一個處理方法:

 public void giveMoney() {
        //調用被代理方法前加入處理方法
        beforeMethod();
        stu.giveMoney();
    }

 

這裡只有一個giveMoney方法,就寫一次beforeMethod方法,但是如果出了giveMonney還有很多其他的方法,那就需要寫很多次beforeMethod方法,麻煩。那看看下麵動態代理如何實現。

     2、動態代理簡單實現

在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler介面,通過這個類和這個介面可以生成JDK動態代理類和動態代理對象。

創建一個動態代理對象步驟,具體代碼見後面:

  • 創建一個InvocationHandler對象
//創建一個與代理對象相關聯的InvocationHandler
 InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);

 

 

  • 使用Proxy類的getProxyClass靜態方法生成一個動態代理類stuProxyClass 
  Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});

 

  • 獲得stuProxyClass 中一個帶InvocationHandler參數的構造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);

 

  • 通過構造器constructor來創建一個動態實例stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);

 

就此,一個動態代理對象就創建完畢,當然,上面四個步驟可以通過Proxy類的newProxyInstances方法來簡化:

 //創建一個與代理對象相關聯的InvocationHandler
  InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//創建一個代理對象stuProxy,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
  Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

 

到這裡肯定都會很疑惑,這動態代理到底是如何執行的,是如何通過代理對象來執行被代理對象的方法的,先不急,我們先看看一個簡單的完整的動態代理的例子。還是上面靜態代理的例子,班長需要幫學生代交班費。
首先是定義一個Person介面:

複製代碼
/**
 * 創建Person介面
 * @author Gonjan
 */
public interface Person {
    //上交班費
    void giveMoney();
}
複製代碼

 

創建需要被代理的實際類:

複製代碼
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    
    @Override
    public void giveMoney() {
        try {
          //假設數錢花了一秒時間
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       System.out.println(name + "上交班費50元");
    }
}
        
複製代碼

 

再定義一個檢測方法執行時間的工具類,在任何方法執行前先調用start方法,執行後調用finsh方法,就可以計算出該方法的運行時間,這也是一個最簡單的方法執行時間檢測工具。

複製代碼
public class MonitorUtil {
    
    private static ThreadLocal<Long> tl = new ThreadLocal<>();
    
    public static void start() {
        tl.set(System.currentTimeMillis());
    }
    
    //結束時列印耗時
    public static void finish(String methodName) {
        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法耗時" + (finishTime - tl.get()) + "ms");
    }
}
複製代碼

創建StuInvocationHandler類,實現InvocationHandler介面,這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,所有執行代理對象的方法都會被替換成執行invoke方法。

再再invoke方法中執行被代理對象target的相應方法。當然,在代理過程中,我們在真正執行被代理對象的方法前加入自己其他處理。這也是Spring中的AOP實現的主要原理,這裡還涉及到一個很重要的關於java反射方面的基礎知識。

複製代碼
public class StuInvocationHandler<T> implements InvocationHandler {
   //invocationHandler持有的被代理對象
    T target;
    
    public StuInvocationHandler(T target) {
       this.target = target;
    }
    
    /**
     * proxy:代表動態代理對象
     * method:代表正在執行的方法
     * args:代表調用目標方法時傳入的實參
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執行" +method.getName() + "方法");
     */   
        //代理過程中插入監測方法,計算該方法耗時
        MonitorUtil.start();
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());
        return result;
    }
}
複製代碼

做完上面的工作後,我們就可以具體來創建動態代理對象了,上面簡單介紹瞭如何創建動態代理對象,我們使用簡化的方式創建動態代理對象:

 

複製代碼
public class ProxyTest {
    public static void main(String[] args) {
        
        //創建一個實例對象,這個對象是被代理的對象
        Person zhangsan = new Student("張三");
        
        //創建一個與代理對象相關聯的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
        
        //創建一個代理對象stuProxy來代理zhangsan,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

       //代理執行上交班費的方法
        stuProxy.giveMoney();
    }
}
複製代碼

 

我們執行這個ProxyTest類,先想一下,我們創建了一個需要被代理的學生張三,將zhangsan對象傳給了stuHandler中,我們在創建代理對象stuProxy時,將stuHandler作為參數了的,上面也有說到所有執行代理對象的方法都會被替換成執行invoke方法,也就是說,最後執行的是StuInvocationHandler中的invoke方法。所以在看到下麵的運行結果也就理所當然了。

運行結果:

上面說到,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。是因為所有被代理執行的方法,都是通過在InvocationHandler中的invoke方法調用的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。例如,這裡的方法計時,所有的被代理對象執行的方法都會被計時,然而我只做了很少的代碼量。

動態代理的過程,代理對象和被代理對象的關係不像靜態代理那樣一目瞭然,清晰明瞭。因為動態代理的過程中,我們並沒有實際看到代理類,也沒有很清晰地的看到代理類的具體樣子,而且動態代理中被代理對象和代理對象是通過InvocationHandler來完成的代理過程的,其中具體是怎樣操作的,為什麼代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行。帶著這些問題,我們就需要對java動態代理的源碼進行簡要的分析,弄清楚其中緣由。

四、動態代理原理分析

    1、Java動態代理創建出來的動態代理類

上面我們利用Proxy類的newProxyInstance方法創建了一個動態代理對象,查看該方法的源碼,發現它只是封裝了創建動態代理類的步驟(紅色標準部分):

複製代碼
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
複製代碼

 

其實,我們最應該關註的是 Class<?> cl = getProxyClass0(loader, intfs);這句,這裡產生了代理類,後面代碼中的構造器也是通過這裡產生的類來獲得,可以看出,這個類的產生就是整個動態代理的關鍵,由於是動態生成的類文件,我這裡不具體進入分析如何產生的這個類文件,只需要知道這個類文件時緩存在java虛擬機中的,我們可以通過下麵的方法將其列印到文件裡面,一睹真容:

複製代碼
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
        String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理類class文件寫入成功");
        } catch (Exception e) {
           System.out.println("寫文件錯誤");
        }
複製代碼

對這個class文件進行反編譯,我們看看jdk為我們生成了什麼樣的內容:

複製代碼
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *註意這裡是生成代理類的構造方法,方法參數為InvocationHandler類型,看到這,是不是就有點明白
  *為何代理對象調用方法都是執行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
  *被代理對象的實例,不禁會想難道是....? 沒錯,就是你想的那樣。
  *
  *super(paramInvocationHandler),是調用父類Proxy的構造方法。
  *父類持有:protected InvocationHandler h;
  *Proxy構造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //這個靜態塊本來是在最後的,我把它拿到前面來,方便描述
   static
  {
    try
    {
      //看看這兒靜態塊兒裡面有什麼,是不是找到了giveMoney方法。請記住giveMoney通過反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
 
  /**
  * 
  *這裡調用代理對象的giveMoney方法,直接就調用了InvocationHandler中的invoke方法,並把m3傳了進去。
  *this.h.invoke(this, m3, null);這裡簡單,明瞭。
  *來,再想想,代理對象持有一個InvocationHandler對象,InvocationHandler對象持有一個被代理的對象,
  *再聯繫到InvacationHandler中的invoke方法。嗯,就是這樣。
  */
  public final void giveMoney()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  //註意,這裡為了節省篇幅,省去了toString,hashCode、equals方法的內容。原理和giveMoney方法一毛一樣。

}
複製代碼

 

jdk為我們的生成了一個叫$Proxy0(這個名字後面的0是編號,有多個代理類會一次遞增)的代理類,這個類文件時放在記憶體中的,我們在創建代理對象時,就是通過反射獲得這個類的構造方法,然後創建的代理實例。通過對這個生成的代理類源碼的查看,我們很容易能看出,動態代理實現的具體過程。

我們可以對InvocationHandler看做一個中介類,中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉為對被代理對象的調用。

代理類調用自己方法時,通過自身持有的中介類對象來調用中介類對象的invoke方法,從而達到代理執行被代理對象的方法。也就是說,動態代理通過中介類實現了具體的代理功能。

五、總結

生成的代理類:$Proxy0 extends Proxy implements Person,我們看到代理類繼承了Proxy類,所以也就決定了java動態代理只能對介面進行代理,Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理。
上面的動態代理的例子,其實就是AOP的一個簡單實現了,在目標對象的方法執行之前和執行之後進行了處理,對方法耗時統計。Spring的AOP實現其實也是用了Proxy和InvocationHandler這兩個東西的。

 

後面補充,知其然,還要知其所以然。

 

該文章為轉載

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 本文是 "Rxjs 響應式編程 第一章:響應式" 這篇文章的學習筆記。 示例代碼地址: "【示例代碼】" 更多文章: "【《大史住在大前端》博文集目錄】" [TOC] 一. 劃重點 三句非常重要的話: 從理念上來理解,Rx模式引入了一種新的 “一切皆流” 的編程範式 從設計模式的角度來看, 是 發佈 ...
  • 國內的設計師大都喜歡用px,而國外的網站大都喜歡用em和rem,那麼三者有什麼區別,又各自有什麼優劣呢? PX特點 1. IE無法調整那些使用px作為單位的字體大小; 2. 國外的大部分網站能夠調整的原因在於其使用了em或rem作為字體單位; 3. Firefox能夠調整px和em,rem,但是96 ...
  • react本身能夠完成動態數據的監聽和更新,如果不是必要可以不適用redux。 安裝redux: cnpm install redux --save,或者yarn add redux。 一、react基本用法 redux是獨立的用於狀態管理的第三方包,它建立狀態機來對單項數據進行管理。 上圖是個人粗 ...
  • 裝飾器模式允許向現有對象中添加新功能,同時又不改變其結構。 介紹 裝飾器模式屬於結構型模式,主要功能是能夠動態地為一個對象添加額外功能。在保證現有功能的基礎上,再添加新功能,可聯想到 WPF 中的附件屬性。 類圖描述 由上圖可知,我們定義了一個基礎介面 IShape 用於約定對象的基礎行為。然後通過 ...
  • 在實際應用中,集群環境里共用一些數據是不可避免的。我的意思是有些數據可以在任何節點進行共用同步讀寫,困難的是如何解決更改衝突問題。本來可以通過分散式資料庫來實現這樣的功能,但使用和維護成本又過高,不值得。分散式數據類型distributed-data (ddata)正是為解決這樣的困局而設計的。ak ...
  • 在上一篇博客中,我“蜻蜓點水”般的介紹了下Java記憶體模型,在這一篇博客,我將帶著大家看下Synchronized關鍵字的那些事,其實把Synchronized關鍵字放到上一篇博客中去介紹,也是符合 “Java記憶體模型”這個標題的,因為Synchronized關鍵字和Java記憶體模型有著密不可分的關 ...
  • 前言 開心一刻 小明的朋友骨折了,小明去他家裡看他。他老婆很細心的為他換藥,敷藥,然後出去買菜。小明滿臉羡慕地說:你特麽真幸福啊,你老婆對你那麼好!朋友哭得稀里嘩啦的說:兄弟你別說了,我幸福個錘子,就是她把我打骨折的。 揣摩下此刻男人的內心 路漫漫其修遠兮,吾將上下而求索! github:https ...
  • 1、資料庫的安裝和連接 2、創建表操作 3、查詢操作 Python查詢Mysql使用 fetchone() 方法獲取單條數據, 使用fetchall() 方法獲取多條數據。 fetchone(): 該方法獲取下一個查詢結果集。結果集是一個對象 fetchall(): 接收全部的返回結果行. rowc ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...