Spring中的增強

来源:http://www.cnblogs.com/zhangzongle/archive/2016/10/10/5944906.html
-Advertisement-
Play Games

在Spring中,目前我學習了幾種增強的方式,和大家分享一下 一:前置增強和後置增強 源碼介紹: 1.User.java package cn.zhang.entity; public class User { private Integer id; // 用戶ID private String u ...


在Spring中,目前我學習了幾種增強的方式,和大家分享一下

之前的話:

1.AOP  (Aspect  Oriented Programming  面向切麵編程)

   在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切麵編程,通過預編譯方式和運行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

面向對象編程是從【靜態角度】考慮程式的結構,而面向切麵編程是從【動態角度】考慮程式運行過程。
AOP底層,就是採用【動態代理】模式實現的。採用了兩種代理:JDK動態代理和CGLIB動態代理。

基本術語(一些名詞):
(1)切麵(Aspect)
切麵泛指[*交叉業務邏輯*]。事務處理和日誌處理可以理解為切麵。常用的切麵有通知(Advice)與顧問(Advisor)。實際就是對主業務邏輯的一種增強。

(2)織入(Weaving)
織入是指將切麵代碼插入到目標對象的過程。代理的invoke方法完成的工作,可以稱為織入。

(3) 連接點(JoinPoint)
連接點是指可以被切麵織入的方法。通常業務介面的方法均為連接點

(4)切入點(PointCut)
切入點指切麵具體織入的方法
註意:被標記為final的方法是不能作為連接點與切入點的。因為最終的是不能被修改的,不能被增強的。

(5)目標對象(Target)
目標對象指將要被增強的對象。即包含主業務邏輯的類的對象。

(6)通知(Advice)
通知是切麵的一種實現,可以完成簡單的織入功能。通知定義了增強代碼切入到目標代碼的時間點,是目標方法執行之前執行,還是執行之後執行等。切入點定義切入的位置,通知定義切入的時間。

(7)顧問(Advisor)
顧問是切麵的另一種實現,能夠將通知以更為複雜的方式織入到目標對象中,是將通知包裝為更複雜切麵的裝配器。

AOP是一種思想,而非實現
AOP是基於OOP,而又遠遠高於OOP,主要是將主要核心業務和交叉業務分離,交叉業務就是切麵。例如,記錄日誌和開啟事務。

一:前置增強和後置增強

源碼介紹:

1.User.java

package cn.zhang.entity;

public class User {
    private Integer id; // 用戶ID
    private String username; // 用戶名
    private String password; // 密碼
    private String email; // 電子郵件
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    
}
View Code

2.IDao.java

package cn.zhang.dao;
//定義介面
import cn.zhang.entity.User;

public interface IDao {
    //定義方法
    public void save(User user);
}
View Code

3.UserDao.java

package cn.zhang.dao.impl;
//實現介面
import cn.zhang.dao.IDao;
import cn.zhang.entity.User;

public class UserDao implements IDao  {

    @Override
    //實現方法
    public void save(User user) {
        System.out.println("save success!");        
    }

}
View Code

4.IUserBiz.java

package cn.zhang.biz;
//業務介面
import cn.zhang.entity.User;

public interface IUserBiz {
    //待處理的方法
     public void save(User user);
}
View Code

5.UserBiz.java

package cn.zhang.biz.impl;
//業務介面的實現類
import cn.zhang.biz.IUserBiz;
import cn.zhang.dao.IDao;
import cn.zhang.entity.User;

public class UserBiz implements IUserBiz {
    //引入IDao介面
    private IDao dao;
    @Override
    //實現方法
    public void save(User user) {
        dao.save(user);
    }
    //dao 屬性的setter訪問器,會被Spring調用,實現設值註入
    public IDao getDao() {
        return dao;
    }
    public void setDao(IDao dao) {
        this.dao = dao;
    }

}
View Code

6.LoggerAfter.java(後置增強)

package cn.zhang.aop;
//後置增強
import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class LoggerAfter implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object arg0, Method arg1, Object[] arg2,
            Object arg3) throws Throwable {
            System.out.println("後置增強代碼");        
    }

}
View Code

7.LoggerBefore.java(前置增強)

package cn.zhang.aop;
//前置增強
import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.springframework.aop.MethodBeforeAdvice;

public class LoggerBefore implements MethodBeforeAdvice {
    private static final Logger log = Logger.getLogger(LoggerBefore.class);
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        log.info("前置內容AAA");    
        System.out.println("前置增強代碼");
    }

}
View Code

8.applicationContext.xml(Spring配置文件)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
        ">
    <bean id="dao" class="cn.zhang.dao.impl.UserDao" />
    <bean id="biz" class="cn.zhang.biz.impl.UserBiz">
        <property name="dao" ref="dao"></property>
    </bean>
    <!-- 定義前置增強組件 -->
    <bean id="loggerBefore" class="cn.zhang.aop.LoggerBefore" />
    <!-- 定義後置增強組件 -->
    <bean id="loggerAfter" class="cn.zhang.aop.LoggerAfter" />
    <!-- 針對AOP的配置 -->
    <aop:config>
        <aop:pointcut id="pointcut"
            expression="execution(public void save(cn.zhang.entity.User))" />
        <!-- 將增強處理和切入點結合在一起,在切入點處插入增強處理,完成"織入" -->
        <aop:advisor pointcut-ref="pointcut" advice-ref="loggerBefore" />
        <aop:advisor pointcut-ref="pointcut" advice-ref="loggerAfter" />
    </aop:config>

</beans>  
View Code

當然,針對AOP的配置也可以使用代理對象 ProxyFactoryBean 代理工廠bean來實現,在測試類中:IUserBiz biz=(IUserBiz)ctx.getBean("serviceProxy");

<!-- 代理對象 ProxyFactoryBean 代理工廠bean -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="targetName" value="biz"></property>
        <property name="interceptorNames" value="loggerBefore,loggerAfter"></property>
    </bean>
View Code

9.MyTest.java

package cn.zhang.test;
//測試類
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.zhang.biz.IUserBiz;
import cn.zhang.entity.User;

public class MyTest {
public static void main(String[] args) {
    
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    IUserBiz biz=(IUserBiz)ctx.getBean("biz");
    User user=new User();
    biz.save(user);
    System.out.println("success!");
}
}
View Code

10.log4j.properties(日誌的配置文件)

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=info, stdout
View Code

當然,別忘了引入我們需要的jar包啊!

常用的jar:

 

二:異常拋出增強和環繞增強

源碼介紹:

1.User.java

package cn.zhang.entity;

public class User {
    private Integer id; // 用戶ID
    private String username; // 用戶名
    private String password; // 密碼
    private String email; // 電子郵件    
    
    public User() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public User(Integer id, String username, String password, String email) {
        super();
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    
}
View Code

2.UserService.java

package cn.zhang.service;

public class UserService {

    public void delete() {
        //int i = 5 / 0;//製造一個錯誤,用於測試異常拋出增強
        System.out.println("delete success!");
    }
}
View Code

3.ErrorLog.java(異常拋出增強)

package cn.zhang.aop;
//異常拋出增強
import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.springframework.aop.ThrowsAdvice;

public class ErrorLog implements ThrowsAdvice {
    private static final Logger log = Logger.getLogger(ErrorLog.class);
    public void afterThrowing(Method method, Object[] args, Object target,
            RuntimeException e){
        log.error(method.getName() + " 方法發生異常:" + e);
    }
}
View Code

4.AroundLog(環繞增強)

package cn.zhang.aop;
//環繞增強
import java.lang.reflect.Method;
import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;

public class AroundLog implements MethodInterceptor {
    
    private static final Logger log = Logger.getLogger(AroundLog.class);
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
    
        Object target=invocation.getThis();//獲取被代理對象
        Method method = invocation.getMethod();//獲得被代理方法
        Object[] args = invocation.getArguments();//獲得方法參數
        
        System.out.println("調用"+target+"的"+method.getName()+"方法。方法參數:"+Arrays.toString(args));
        Object result;//調用目標方法,獲取目標方法返回值
        try {
            result = invocation.proceed();
            System.out.println("調用" + target + "的" + method.getName()
                    + "方法。方法返回值:" + result);
            return result;
        } catch (Exception e) {
            log.error(method.getName()+"方法發生異常:"+e);
            throw e;
        }
        
    }

}
View Code

5.applicationContext.xml(Spring配置文件)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
        ">
    <bean id="service" class="cn.zhang.service.UserService" />
    <!-- 異常拋出增強 -->
    <!-- <bean id="error" class="cn.zhang.aop.ErrorLog"/> -->
    <!-- 環繞增強 -->
    <bean id="error" class="cn.zhang.aop.AroundLog"/>
    
    <aop:config>
        <aop:pointcut expression="execution(public void delete())"
            id="pointcut" />
        <aop:advisor advice-ref="error" pointcut-ref="pointcut" />
    </aop:config>
</beans> 
View Code

6.MyTest.java

package cn.zhang.test;
//測試類
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.zhang.service.UserService;

public class MyTest {
    public static void main(String[] args) {
        
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        UserService service = (UserService) ctx.getBean("service");
        try {
            service.delete();
        } catch (Exception e) {
            System.out.println("錯誤了");
        }
        System.out.println("success!");
    }
}
View Code

三:註解增強方式實現前置增強和後置增強

源碼介紹:

1.UserService.java

package cn.service;
//業務處理類
public class UserService {
    //方法
    public void delete() {
        System.out.println("delete success!");
    }
}
View Code

2.AnnotationAdvice.java(註解增強)

package cn.aop;
//註解增強
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationAdvice {
    
    // 定義前置增強
    @Before("execution(* cn.service.UserService.*(..))")
    public void before(JoinPoint jp) {
        System.out.println("調用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,參數:"+jp.getArgs()+",參數個數:"+jp.getArgs().length);
        System.out.println("before");
    }
    // 定義後置增強
    @AfterReturning(pointcut="execution(* cn.service.UserService.*(..))",returning="returnValue")
    public void afterReturning(JoinPoint jp,Object returnValue) { 
        System.out.println("調用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,參數:"+jp.getArgs()+",返回值為:"+returnValue);
        System.out.println("after");
    }
}
View Code

註:

java.lang.Object[] getArgs():獲取連接點方法運行時的入參列表
Signature getSignature() :獲取連接點的方法簽名對象
java.lang.Object getTarget() :獲取連接點所在的目標對象
java.lang.Object getThis() :獲取代理對象本身

3.applicationContext.xml(Spring配置文件)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
        ">
        
    <bean id="service" class="cn.service.UserService" />

    <bean id="error" class="cn.aop.AnnotationAdvice" />

    <!-- 針對AOP的配置 -->
    <aop:aspectj-autoproxy />
</beans> 
View Code

4.MyTest.java

package cn.test;
//註解增強測試
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.service.UserService;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService biz=(UserService)ctx.getBean("service");
        biz.delete();
        System.out.println("success!");
    }

}
View Code

四: 

通知Advice是Spring提供的一種切麵(Aspect)。但其功能過於簡單,只能
將切麵織入到目標類的所有目標方法中,無法完成將切麵織入到指定目標方法中。

顧問Advisor是Spring提供的另一種切麵。其可以完成更為複雜的切麵織入功能。PointcutAdvisor是顧問的一種,可以指定具體
的切入點。顧問將通知進行了包裝,會根據不同的通知類型,在不同的時間點,將切麵織入到不同的切入點。
PointcutAdvisor介面有兩個較為常用的實現類:
*:NameMatchMethodPointcutAdvisor 名稱匹配方法切入點顧問
*:RegexpMethodPointcutAdvisor 正則表達式匹配方法切入點顧問
<property name="pattern" value=".*do.*"></property> 表示方法全名(包名,介面名,方法名)
運算符 名稱 意義
. 點號 表示任意單個字元
+ 加號 表示前一個字元出現一次或者多次
* 星號 表示前一個字元出現0次或者多次
=====預設Advisor自動代理生成器
DefaultAdvisorAutoProxyCreator
=====BeanName自動代理生成器
BeanNameAutoProxyCreator

實例:

 

源碼介紹:

1.ISomeService.java

package service;
//介面
public interface ISomeService {
    //待實現的方法
   public void doFirst();
   public void doSecond();
}
View Code

2.SomeServiceImpl.java

package service;
//介面實現類
public class SomeServiceImpl implements ISomeService {
    //實現介面定義的方法
    @Override
    public void doFirst() {
        System.out.println("方法A");
    }

    @Override
    public void doSecond() {
        System.out.println("方法B");    
    }

}
View Code

3.MyMethodBeforeAdvice.java

package aop;
//前置增強
import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class MyMethodBeforeAdvice implements MethodBeforeAdvice{
    
    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
            System.out.println("目標方法執行之前執行");
    }

}
View Code

4.applicationContext.xml(Spring配置文件)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 目標對象 -->
    <bean id="someService" class="service.SomeServiceImpl"></bean>

    <!-- 切麵:通知 -->
    <bean id<

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

-Advertisement-
Play Games
更多相關文章
  • 模型 1. 獲取key ftok() 2. 創建/獲取信號量集 semget() 3. 初始化信號量集 semctl() 4. 操作信號量集 semop() 3. 刪除信號量集 semctl() 使用的頭文件: ftok() pathname :文件名 proj_id : 1~255的一個數,表示p ...
  • 剛開始學習python,首先要瞭解一下python解釋器。 什麼是python解釋器? 編寫python代碼保存後,我們會得到一個以.py為擴展名的文本文件。要運行此文件,就需要python解釋器去執行.py文件。這裡,我們介紹3種解釋器。 1、CPython 當我們從Python官方網站下載並安裝 ...
  • 本文章向碼農們介紹 php 給圖片加水印的兩種方法,感興趣的碼農可以參考一下本文章的源代碼。 方法一:PHP最簡單的加水印方法 方法二:php給圖片加文字水印 原文地址:http://www.manongjc.com/article/593.html ...
  • 建議106:動態代理可以使代理模式更加靈活 Java的反射框架提供了動態代理(Dynamic Proxy)機制,允許在運行期對目標類生成代理,避免重覆開發。我們知道一個靜態代理是通過主題角色(Proxy)和具體主題角色(Real Subject)共同實現主題角色(Subject)的邏輯的,只是代理角 ...
  • python的字典是一個非常方便的數據結構,使用它我們可以輕易的根據姓名(鍵)來找到他的成績,排名等(值),而不用去遍歷整個數據集。 例如:{'Lee': [1, 100], 'Jane': [2, 98]...} 但是在使用字典的過程中產生了一些問題,那就是,字典本身是不管你錄入的順序的 當有這種 ...
  • xml已經被json逐漸替代,現在用的api都是用貌似用的json,但是有些老的網站還是在用xml。 這裡預設xml文件為:address.xml,存放在和讀取的php文件相同級別目錄,xml內容如下: xml讀取方式一: xml讀取方式二: ...
  • 在這個問題中,我們期望得到的結果是找到這三輪比賽中,每輪都進球的球員都有誰。下麵用python來模擬一下,先生成一批數據: 如上代碼所示我們生成了三輪比賽的數據,想要得到三輪比賽中,哪位球員在每輪比賽都進球,有這麼幾種方法: 一. 遍歷 這種方法效率不高,並且笨重 二. 與運算 與運算清晰明瞭,利用 ...
  • 1.IOC和DI IOC和DI是Spring核心思想不同方面的描述,IOC和DI是差不多的概念,重要特征是介面依賴,是把對象關係推遲到運行時去確定 1.1控制反轉(Inversion of Control): 控制反轉是一個重要的面向以對象編程的法則來削減電腦程式的耦合問題,也是輕量級Spring ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...