精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Spring Boot 插件化開發模式,即插即用

開發
插件化開發模式正在很多編程語言或技術框架中得以廣泛的應用實踐,那么為什么要使用插件呢?

一、前言

插件化開發模式正在很多編程語言或技術框架中得以廣泛的應用實踐,比如大家熟悉的jenkins,docker可視化管理平臺rancher,以及日常編碼使用的編輯器idea,vscode等,隨處可見的帶有熱插拔功能的插件,讓系統像插了翅膀一樣,大大提升了系統的擴展性和伸縮性,也拓展了系統整體的使用價值,那么為什么要使用插件呢?

1. 使用插件的好處

(1) 模塊解耦

實現服務模塊之間解耦的方式有很多,但是插件來說,其解耦的程度似乎更高,而且更靈活,可定制化、個性化更好。

舉例來說,代碼中可以使用設計模式來選擇使用哪種方式發送短信給下單完成的客戶,問題是各個短信服務商并不一定能保證在任何情況下都能發送成功,怎么辦呢?這時候設計模式也沒法幫你解決這個問題,如果使用定制化插件的方式,結合外部配置參數,假設系統中某種短信發送不出去了,這時候就可以利用插件動態植入,切換為不同的廠商發短信了。

(2) 提升擴展性和開放性

以spring來說,之所以具備如此廣泛的生態,與其自身內置的各種可擴展的插件機制是分不開的,試想為什么使用了spring框架之后可以很方便的對接其他中間件,那就是spring框架提供了很多基于插件化的擴展點。

插件化機制讓系統的擴展性得以提升,從而可以豐富系統的周邊應用生態。

(3) 方便第三方接入

有了插件之后,第三方應用或系統如果要對接自身的系統,直接基于系統預留的插件接口完成一套適合自己業務的實現即可,而且對自身系統的侵入性很小,甚至可以實現基于配置參數的熱加載,方便靈活,開箱即用。

2. 插件化常用實現思路

以java為例,這里結合實際經驗,整理一些常用的插件化實現思路:

  • spi機制;
  • 約定配置和目錄,利用反射配合實現;
  • springboot中的Factories機制;
  • java agent(探針)技術;
  • spring內置擴展點;
  • 第三方插件包,例如:spring-plugin-core;
  • spring aop技術。

二、Java常用插件實現方案

1. serviceloader方式

serviceloader是java提供的spi模式的實現。按照接口開發實現類,而后配置,java通過ServiceLoader來實現統一接口不同實現的依次調用。而java中最經典的serviceloader的使用就是Java的spi機制。

(1) java spi

SPI全稱 Service Provider Interface ,是JDK內置的一種服務發現機制,SPI是一種動態替換擴展機制,比如有個接口,你想在運行時動態給他添加實現,你只需按照規范給他添加一個實現類即可。比如大家熟悉的jdbc中的Driver接口,不同的廠商可以提供不同的實現,有mysql的,也有oracle的,而Java的SPI機制就可以為某個接口尋找服務的實現。

下面用一張簡圖說明下SPI機制的原理:

(2) java spi 簡單案例

如下工程目錄,在某個應用工程中定義一個插件接口,而其他應用工程為了實現這個接口,只需要引入當前工程的jar包依賴進行實現即可,這里為了演示我就將不同的實現直接放在同一個工程下。

定義接口:

public interface MessagePlugin {
 
    public String sendMsg(Map msgMap);
 
}

定義兩個不同的實現:

public class AliyunMsg implements MessagePlugin {

    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("aliyun sendMsg");
        return"aliyun sendMsg";
    }
}
publicclass TencentMsg implements MessagePlugin {

    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("tencent sendMsg");
        return"tencent sendMsg";
    }
}

在resources目錄按照規范要求創建文件目錄,并填寫實現類的全類名。

自定義服務加載類:

public static void main(String[] args) {
        ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);
        Iterator<MessagePlugin> iterator = serviceLoader.iterator();
        Map map = new HashMap();
        while (iterator.hasNext()){
            MessagePlugin messagePlugin = iterator.next();
            messagePlugin.sendMsg(map);
        }
    }

運行上面的程序后,可以看到下面的效果,這就是說,使用ServiceLoader的方式可以加載到不同接口的實現,業務中只需要根據自身的需求,結合配置參數的方式就可以靈活的控制具體使用哪一個實現。

2. 自定義配置約定方式

serviceloader其實是有缺陷的,在使用中必須在META-INF里定義接口名稱的文件,在文件中才能寫上實現類的類名,如果一個項目里插件化的東西比較多,那很可能會出現越來越多配置文件的情況。所以在結合實際項目使用時,可以考慮下面這種實現思路:

  • A應用定義接口;
  • B,C,D等其他應用定義服務實現;
  • B,C,D應用實現后達成SDK的jar;
  • A應用引用SDK或者將SDK放到某個可以讀取到的目錄下;
  • A應用讀取并解析SDK中的實現類;
  • 在上文中案例基礎上,我們做如下調整。

(1) 添加配置文件

在配置文件中,將具體的實現類配置進去。

server :
  port : 8081
impl:
  name : com.congge.plugins.spi.MessagePlugin
  clazz :
    - com.congge.plugins.impl.TencentMsg
    - com.congge.plugins.impl.AliyunMsg

(2) 自定義配置文件加載類

通過這個類,將上述配置文件中的實現類封裝到類對象中,方便后續使用。

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("impl")
@ToString
publicclass ClassImpl {
    @Getter
    @Setter
    String name;

    @Getter
    @Setter
    String[] clazz;
}

(3) 自定義測試接口

使用上述的封裝對象通過類加載的方式動態的在程序中引入。

import com.congge.config.ClassImpl;
import com.congge.plugins.spi.MessagePlugin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
publicclass SendMsgController {

    @Autowired
    ClassImpl classImpl;

    //localhost:8081/sendMsg
    @GetMapping("/sendMsg")
    public String sendMsg() throws Exception{
        for (int i=0;i<classImpl.getClazz().length;i++) {
            Class pluginClass= Class.forName(classImpl.getClazz()[i]);
            MessagePlugin messagePlugin = (MessagePlugin) pluginClass.newInstance();
            messagePlugin.sendMsg(new HashMap());
        }
        return"success";
    }

}

(4) 啟動類

@EnableConfigurationProperties({ClassImpl.class})
@SpringBootApplication
public class PluginApp {
 
    public static void main(String[] args) {
        SpringApplication.run(PluginApp.class,args);
    }
 
}

啟動工程代碼后,調用接口:localhost:8081/sendMsg,在控制臺中可以看到下面的輸出信息,即通過這種方式也可以實現類似serviceloader的方式,不過在實際使用時,可以結合配置參數進行靈活的控制。

3. 自定義配置讀取依賴jar的方式

更進一步,在很多場景下,可能我們并不想直接在工程中引入接口實現的依賴包,這時候可以考慮通過讀取指定目錄下的依賴jar的方式,利用反射的方式進行動態加載,這也是生產中一種比較常用的實踐經驗。

具體實踐來說,主要為下面的步驟:

  • 應用A定義服務接口;
  • 應用B,C,D等實現接口(或者在應用內部實現相同的接口);
  • 應用B,C,D打成jar,放到應用A約定的讀取目錄下;
  • 應用A加載約定目錄下的jar,通過反射加載目標方法。

在上述的基礎上,按照上面的實現思路來實現一下。

(1) 創建約定目錄

在當前工程下創建一個lib目錄,并將依賴的jar放進去:

(2) 新增讀取jar的工具類

添加一個工具類,用于讀取指定目錄下的jar,并通過反射的方式,結合配置文件中的約定配置進行反射方法的執行。

@Component
publicclass ServiceLoaderUtils {

    @Autowired
    ClassImpl classImpl;


    public static void loadJarsFromAppFolder() throws Exception {
        String path = "E:\\code-self\\bitzpp\\lib";
        File f = new File(path);
        if (f.isDirectory()) {
            for (File subf : f.listFiles()) {
                if (subf.isFile()) {
                    loadJarFile(subf);
                }
            }
        } else {
            loadJarFile(f);
        }
    }

    public static void loadJarFile(File path) throws Exception {
        URL url = path.toURI().toURL();
        // 可以獲取到AppClassLoader,可以提到前面,不用每次都獲取一次
        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        // 加載
        //Method method = URLClassLoader.class.getDeclaredMethod("sendMsg", Map.class);
        Method method = URLClassLoader.class.getMethod("sendMsg", Map.class);

        method.setAccessible(true);
        method.invoke(classLoader, url);
    }

    public  void main(String[] args) throws Exception{
        System.out.println(invokeMethod("hello"));;
    }

    public String doExecuteMethod() throws Exception{
        String path = "E:\\code-self\\bitzpp\\lib";
        File f1 = new File(path);
        Object result = null;
        if (f1.isDirectory()) {
            for (File subf : f1.listFiles()) {
                //獲取文件名稱
                String name = subf.getName();
                String fullPath = path + "\\" + name;
                //執行反射相關的方法
                //ServiceLoaderUtils serviceLoaderUtils = new ServiceLoaderUtils();
                //result = serviceLoaderUtils.loadMethod(fullPath);
                File f = new File(fullPath);
                URL urlB = f.toURI().toURL();
                URLClassLoader classLoaderA = new URLClassLoader(new URL[]{urlB}, Thread.currentThread()
                        .getContextClassLoader());
                String[] clazz = classImpl.getClazz();
                for(String claName : clazz){
                    if(name.equals("biz-pt-1.0-SNAPSHOT.jar")){
                        if(!claName.equals("com.congge.spi.BitptImpl")){
                            continue;
                        }
                        Class<?> loadClass = classLoaderA.loadClass(claName);
                        if(Objects.isNull(loadClass)){
                            continue;
                        }
                        //獲取實例
                        Object obj = loadClass.newInstance();
                        Map map = new HashMap();
                        //獲取方法
                        Method method=loadClass.getDeclaredMethod("sendMsg",Map.class);
                        result = method.invoke(obj,map);
                        if(Objects.nonNull(result)){
                            break;
                        }
                    }elseif(name.equals("miz-pt-1.0-SNAPSHOT.jar")){
                        if(!claName.equals("com.congge.spi.MizptImpl")){
                            continue;
                        }
                        Class<?> loadClass = classLoaderA.loadClass(claName);
                        if(Objects.isNull(loadClass)){
                            continue;
                        }
                        //獲取實例
                        Object obj = loadClass.newInstance();
                        Map map = new HashMap();
                        //獲取方法
                        Method method=loadClass.getDeclaredMethod("sendMsg",Map.class);
                        result = method.invoke(obj,map);
                        if(Objects.nonNull(result)){
                            break;
                        }
                    }
                }
                if(Objects.nonNull(result)){
                    break;
                }
            }
        }
        return result.toString();
    }

    public Object loadMethod(String fullPath) throws Exception{
        File f = new File(fullPath);
        URL urlB = f.toURI().toURL();
        URLClassLoader classLoaderA = new URLClassLoader(new URL[]{urlB}, Thread.currentThread()
                .getContextClassLoader());
        Object result = null;
        String[] clazz = classImpl.getClazz();
        for(String claName : clazz){
            Class<?> loadClass = classLoaderA.loadClass(claName);
            if(Objects.isNull(loadClass)){
                continue;
            }
            //獲取實例
            Object obj = loadClass.newInstance();
            Map map = new HashMap();
            //獲取方法
            Method method=loadClass.getDeclaredMethod("sendMsg",Map.class);
            result = method.invoke(obj,map);
            if(Objects.nonNull(result)){
                break;
            }
        }
        return result;
    }


    public static String invokeMethod(String text) throws Exception{
        String path = "E:\\code-self\\bitzpp\\lib\\miz-pt-1.0-SNAPSHOT.jar";
        File f = new File(path);
        URL urlB = f.toURI().toURL();
        URLClassLoader classLoaderA = new URLClassLoader(new URL[]{urlB}, Thread.currentThread()
                .getContextClassLoader());
        Class<?> product = classLoaderA.loadClass("com.congge.spi.MizptImpl");
        //獲取實例
        Object obj = product.newInstance();
        Map map = new HashMap();
        //獲取方法
        Method method=product.getDeclaredMethod("sendMsg",Map.class);
        //執行方法
        Object result1 = method.invoke(obj,map);
        // TODO According to the requirements , write the implementation code.
        return result1.toString();
    }

    public static String getApplicationFolder() {
        String path = ServiceLoaderUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        returnnew File(path).getParent();
    }



}

(3) 添加測試接口

添加如下測試接口:

@GetMapping("/sendMsgV2")
public String index() throws Exception {
    String result = serviceLoaderUtils.doExecuteMethod();
    return result;
}

以上全部完成之后,啟動工程,測試一下該接口,仍然可以得到預期結果。

在上述的實現中還是比較粗糙的,實際運用時,還需要做較多的優化改進以滿足實際的業務需要,比如接口傳入類型參數用于控制具體使用哪個依賴包的方法進行執行等。

三、SpringBoot中的插件化實現

在大家使用較多的springboot框架中,其實框架自身提供了非常多的擴展點,其中最適合做插件擴展的莫過于spring.factories的實現。

1. Spring Boot中的SPI機制

在Spring中也有一種類似與Java SPI的加載機制。它在META-INF/spring.factories文件中配置接口的實現類名稱,然后在程序中讀取這些配置文件并實例化,這種自定義的SPI機制是Spring Boot Starter實現的基礎。

2. Spring Factories實現原理

spring-core包里定義了SpringFactoriesLoader類,這個類實現了檢索META-INF/spring.factories文件,并獲取指定接口的配置的功能。在這個類中定義了兩個對外的方法:

  • loadFactories 根據接口類獲取其實現類的實例,這個方法返回的是對象列表;
  • loadFactoryNames 根據接口獲取其接口類的名稱,這個方法返回的是類名的列表。

上面的兩個方法的關鍵都是從指定的ClassLoader中獲取spring.factories文件,并解析得到類名列表,具體代碼如下:

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    catch (IOException ex) {
        thrownew IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

從代碼中我們可以知道,在這個方法中會遍歷整個ClassLoader中所有jar包下的spring.factories文件,就是說我們可以在自己的jar中配置spring.factories文件,不會影響到其它地方的配置,也不會被別人的配置覆蓋。

spring.factories的是通過Properties解析得到的,所以我們在寫文件中的內容都是安裝下面這種方式配置的:

com.xxx.interface=com.xxx.classname

如果一個接口希望配置多個實現類,可以使用’,’進行分割。

3. Spring Factories案例實現

接下來看一個具體的案例實現來體驗下Spring Factories的使用:

(1) 定義一個服務接口

自定義一個接口,里面添加一個方法。

public interface SmsPlugin {
 
    public void sendMessage(String message);
 
}

(2) 定義2個服務實現

實現類1:

public class BizSmsImpl implements SmsPlugin {
 
    @Override
    public void sendMessage(String message) {
        System.out.println("this is BizSmsImpl sendMessage..." + message);
    }
}

實現類2:

public class SystemSmsImpl implements SmsPlugin {
 
    @Override
    public void sendMessage(String message) {
        System.out.println("this is SystemSmsImpl sendMessage..." + message);
    }
}

(3) 添加spring.factories文件

在resources目錄下,創建一個名叫:META-INF的目錄,然后在該目錄下定義一個spring.factories的配置文件,內容如下,其實就是配置了服務接口,以及兩個實現類的全類名的路徑。

com.congge.plugin.spi.SmsPlugin=\
com.congge.plugin.impl.SystemSmsImpl,\
com.congge.plugin.impl.BizSmsImpl

(4) 添加自定義接口

添加一個自定義的接口,有沒有發現,這里和java 的spi有點類似,只不過是這里換成了SpringFactoriesLoader去加載服務。

@GetMapping("/sendMsgV3")
public String sendMsgV3(String msg) throws Exception{
    List<SmsPlugin> smsServices= SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
    for(SmsPlugin smsService : smsServices){
        smsService.sendMessage(msg);
    }
    return "success";
}

啟動工程之后,調用一下該接口進行測試,localhost:8087/sendMsgV3?msg=hello,通過控制臺,可以看到,這種方式能夠正確獲取到系統中可用的服務實現。

利用spring的這種機制,可以很好的對系統中的某些業務邏輯通過插件化接口的方式進行擴展實現。

四、插件化機制案例實戰

結合上面掌握的理論知識,下面基于Java SPI機制進行一個接近真實使用場景的完整的操作步驟:

1. 案例背景

  • 3個微服務模塊,在A模塊中有個插件化的接口;
  • 在A模塊中的某個接口,需要調用插件化的服務實現進行短信發送;
  • 可以通過配置文件配置參數指定具體的哪一種方式發送短信;
  • 如果沒有加載到任何插件,將走A模塊在默認的發短信實現。

(1) 模塊結構

  • biz-pp,插件化接口工程;
  • bitpt,aliyun短信發送實現;
  • miz-pt,tencent短信發送實現。

(2) 整體實現思路

本案例完整的實現思路參考如下:

  • biz-pp定義服務接口,并提供出去jar被其他實現工程依賴;
  • bitpt與miz-pt依賴biz-pp的jar并實現SPI中的方法;
  • bitpt與miz-pt按照API規范實現完成后,打成jar包,或者安裝到倉庫中;
  • biz-pp在pom中依賴bitpt與miz-pt的jar,或者通過啟動加載的方式即可得到具體某個實現。

2. biz-pp 關鍵代碼實現過程

(1) 添加服務接口

public interface MessagePlugin {
 
    public String sendMsg(Map msgMap);
 
}

(2) 打成jar包并安裝到倉庫

這一步比較簡單就不展開了。

(3) 自定義服務加載工具類

這個類,可以理解為在真實的業務編碼中,可以根據業務定義的規則,具體加載哪個插件的實現類進行發送短信的操作:

import com.congge.plugin.spi.MessagePlugin;
import com.congge.spi.BitptImpl;
import com.congge.spi.MizptImpl;

import java.util.*;

publicclass PluginFactory {

    public void installPlugin(){
        Map context = new LinkedHashMap();
        context.put("_userId","");
        context.put("_version","1.0");
        context.put("_type","sms");
        ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);
        Iterator<MessagePlugin> iterator = serviceLoader.iterator();
        while (iterator.hasNext()){
            MessagePlugin messagePlugin = iterator.next();
            messagePlugin.sendMsg(context);
        }
    }

    public static MessagePlugin getTargetPlugin(String type){
        ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);
        Iterator<MessagePlugin> iterator = serviceLoader.iterator();
        List<MessagePlugin> messagePlugins = new ArrayList<>();
        while (iterator.hasNext()){
            MessagePlugin messagePlugin = iterator.next();
            messagePlugins.add(messagePlugin);
        }
        MessagePlugin targetPlugin = null;
        for (MessagePlugin messagePlugin : messagePlugins) {
            boolean findTarget = false;
            switch (type) {
                case"aliyun":
                    if (messagePlugin instanceof BitptImpl){
                        targetPlugin = messagePlugin;
                        findTarget = true;
                        break;
                    }
                case"tencent":
                    if (messagePlugin instanceof MizptImpl){
                        targetPlugin = messagePlugin;
                        findTarget = true;
                        break;
                    }
            }
            if(findTarget) break;
        }
        return targetPlugin;
    }

    public static void main(String[] args) {
        new PluginFactory().installPlugin();
    }


}

(4) 自定義接口

@RestController
publicclass SmsController {

    @Autowired
    private SmsService smsService;

    @Autowired
    private ServiceLoaderUtils serviceLoaderUtils;

    //localhost:8087/sendMsg?msg=sendMsg
    @GetMapping("/sendMsg")
    public String sendMessage(String msg){
        return smsService.sendMsg(msg);
    }

}

(5) 接口實現

@Service
publicclass SmsService {

    @Value("${msg.type}")
    private String msgType;

    @Autowired
    private DefaultSmsService defaultSmsService;

    public String sendMsg(String msg) {
        MessagePlugin messagePlugin = PluginFactory.getTargetPlugin(msgType);
        Map paramMap = new HashMap();
        if(Objects.nonNull(messagePlugin)){
            return messagePlugin.sendMsg(paramMap);
        }
        return defaultSmsService.sendMsg(paramMap);
    }
}

(6) 添加服務依賴

在該模塊中,需要引入對具體實現的兩個工程的jar依賴(也可以通過啟動加載的命令方式):

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--依賴具體的實現-->
    <dependency>
        <groupId>com.congge</groupId>
        <artifactId>biz-pt</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>com.congge</groupId>
        <artifactId>miz-pt</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

</dependencies>

biz-pp的核心代碼實現就到此結束了,后面再具體測試的時候再繼續。

3. bizpt 關鍵代碼實現過程

接下來就是插件化機制中具體的SPI實現過程,兩個模塊的實現步驟完全一致,挑選其中一個說明,工程目錄結構如下:

(1) 添加對biz-app的jar的依賴

將上面biz-app工程打出來的jar依賴過來:

<dependencies>
    <dependency>
        <groupId>com.congge</groupId>
        <artifactId>biz-app</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

(2) 添加MessagePlugin接口的實現

public class BitptImpl implements MessagePlugin {
 
    @Override
    public String sendMsg(Map msgMap) {
        Object userId = msgMap.get("userId");
        Object type = msgMap.get("_type");
        //TODO 參數校驗
        System.out.println(" ==== userId :" + userId + ",type :" + type);
        System.out.println("aliyun send message success");
        return "aliyun send message success";
    }
}

(3) 添加SPI配置文件

按照前文的方式,在resources目錄下創建一個文件,注意文件名稱為SPI中的接口全名,文件內容為實現類的全類名。

com.congge.spi.BitptImpl

(4) 將jar安裝到倉庫中

完成實現類的編碼后,通過maven命令將jar安裝到倉庫中,然后再在上一步的biz-app中引入即可。

4. 效果演示

啟動biz-app服務,調用接口:localhost:8087/sendMsg?msg=sendMsg,可以看到如下效果:

為什么會出現這個效果呢?因為我們在實現類配置了具體使用哪一種方式進行短信的發送,而加載插件的時候正好能夠找到對應的服務實現,這樣的話就給當前的業務提供了一個較好的擴展點。

責任編輯:趙寧寧 來源: 程序員小富
相關推薦

2025-07-02 10:06:32

2025-01-02 11:20:47

2025-07-04 09:31:56

2023-07-10 08:44:00

2009-06-30 19:12:16

云計算SOAIT

2025-02-11 07:55:45

2009-08-19 16:59:51

美國西蒙光纜結構綜合布線

2011-11-11 16:29:00

斐訊電力貓FH2001

2022-06-10 10:38:07

數據中心模塊化設計服務器

2019-09-23 16:06:50

物聯網大數據IOT

2017-09-19 10:11:57

德國電信

2024-11-12 10:20:00

模型數據

2024-01-15 06:40:00

研究視頻

2013-02-26 12:14:17

華為解決方案電調天線

2024-07-17 09:38:26

2025-11-03 08:44:00

2019-08-21 14:34:41

2010-06-02 15:09:37

IPv6網絡地址

2024-12-26 00:51:38

2012-10-08 15:46:35

酷樂視投影機
點贊
收藏

51CTO技術棧公眾號

欧美午夜美女看片| 欧美韩日一区| 国产精品国产三级国产有无不卡| 国产欧美精品一区二区三区-老狼 国产欧美精品一区二区三区介绍 国产欧美精品一区二区 | 精品一区二区国语对白| 欧美乱妇40p| 一女三黑人理论片在线| 高清视频在线观看三级| 国产成人精品亚洲日本在线桃色| 国产精品a久久久久久| 蜜臀av午夜精品久久| 成人av影音| 日韩欧美精品在线观看| 国产又粗又爽又黄的视频| 婷婷在线免费观看| 视频一区二区欧美| 九色精品免费永久在线| 亚洲区免费视频| 97se亚洲国产一区二区三区| 精品视频一区三区九区| 老司机午夜网站| 人人妻人人澡人人爽久久av | 国产激情久久久久| 久久精品99国产精| 99国内精品久久久久久久| 欧美丰满美乳xxx高潮www| 日本丰满大乳奶| 日本不卡三区| 成人亚洲精品久久久久软件| 国产成人精品国内自产拍免费看| 激情五月色婷婷| 日韩欧美精品一区| 亚洲韩国欧洲国产日产av| 日韩av在线播放不卡| 在线你懂的视频| 亚洲日本护士毛茸茸| 免费观看成人在线| 免费黄色小视频在线观看| 亚洲激情网址| 欧美精品福利视频| 免费在线视频观看| 欧美在线三级| 欧美大码xxxx| 国产精品视频一区二区三| 久久在线播放| 亚洲精品自拍第一页| 少妇极品熟妇人妻无码| 成人日韩av| 欧洲精品一区二区三区在线观看| 99精品人妻少妇一区二区| www在线视频| 中文欧美字幕免费| 午夜老司机精品| 最新国产在线观看| 欧美国产日本韩| 成人9ⅰ免费影视网站| 国产强伦人妻毛片| 久久视频一区| 午夜精品久久久久久久久久久久| 日韩美女黄色片| 欧美三级小说| 欧美放荡办公室videos4k| 久久中文字幕无码| aⅴ色国产欧美| 久久久国产精品x99av| 懂色av蜜桃av| 在线观看日韩| 久久精品中文字幕电影| 美国黄色小视频| 一区二区影院| 992tv在线成人免费观看| 国产欧美一区二区三区在线看蜜臂| 日韩视频在线一区二区三区| 九九热这里只有精品6| 久草资源在线视频| 国产视频亚洲| 国产成人高清激情视频在线观看| 亚洲av无码乱码国产精品fc2| 日韩在线a电影| 91久久精品国产91久久| 97久久人国产精品婷婷| 成人午夜视频在线观看| 欧美一级爱爱| 美女毛片在线看| 国产精品传媒在线| 国产肉体ⅹxxx137大胆| 黄色在线免费观看网站| 黄色精品在线看| 国内自拍视频一区| 精品国产免费人成网站| 91精品国产高清一区二区三区蜜臀 | 午夜精品免费在线| 日韩视频免费在线播放| 精品视频在线播放一区二区三区| 亚洲福利小视频| 国产麻豆天美果冻无码视频| 97精品国产| 欧美激情第三页| 波多野结衣电车| 国产成人精品免费视频网站| 欧美日韩精品免费看| 黄色国产网站在线播放| 岛国精品视频在线播放| 尤物网站在线看| 激情亚洲另类图片区小说区| 在线日韩欧美视频| 日本少妇在线观看| 男女男精品网站| 久久精品magnetxturnbtih| 国产私拍精品| 亚洲18色成人| 少妇愉情理伦片bd| 精品国产成人| 久久偷看各类女兵18女厕嘘嘘| 69视频免费在线观看| 久久99精品一区二区三区三区| 91日韩在线播放| 国产免费视频在线| 天天综合色天天综合色h| 一区二区三区国产免费| 成人在线观看免费播放| 91麻豆精品国产91久久久资源速度 | 日韩视频1区| 日韩一区二区欧美| 日韩少妇高潮抽搐| 国产成人综合视频| 日韩一本精品| av中文字幕在线看| 欧美一级二级三级蜜桃| 91大神福利视频| 日本一区中文字幕| 久久国产精品久久| 搞黄网站在线看| 欧美日韩dvd在线观看| 91精品啪在线观看国产| 欧美日韩亚洲国产精品| 国产精品老牛影院在线观看| 欧美一级淫片aaaaaa| 一区二区三区四区在线| 在线a免费观看| 天堂在线精品| 91精品国产91久久久久久不卡| 中文在线字幕av| 国产精品色眯眯| 久久人人爽av| 日韩一区电影| 国产美女精品视频| 日本中文字幕电影在线免费观看| 欧美伊人久久久久久午夜久久久久| 韩国三级hd中文字幕有哪些| 自拍偷拍欧美| av激情久久| 久久不射影院| 精品欧美乱码久久久久久| 久久黄色免费视频| eeuss国产一区二区三区| 免费一级特黄毛片| 99精品国产一区二区三区2021 | 影音先锋成人资源网站| 日韩在线亚洲| 欧美国产中文字幕| 亚洲精品字幕在线| 五月婷婷欧美视频| 麻豆传媒在线看| 欧美精品九九| 久久久www免费人成黑人精品| 黄网在线免费看| 亚洲精品国产suv| 无码人妻av一区二区三区波多野 | 久久精品动漫| 日韩欧美国产二区| 国内不卡的一区二区三区中文字幕| 中文字幕日韩欧美| 国产av无码专区亚洲a∨毛片| 国产欧美精品一区二区三区四区 | 亚洲热av色在线播放| 欧美成人精品h版在线观看| 97久久人国产精品婷婷| 亚洲成人av一区| 久久久亚洲av波多野结衣| 久久一区激情| 四虎4hu永久免费入口| jazzjazz国产精品麻豆| 欧美尺度大的性做爰视频| 欧美一区二区公司| 在线观看日韩电影| 91网站免费视频| 日韩av中文字幕一区二区| 潘金莲一级淫片aaaaaa播放1| www国产精品| 青青草一区二区| 中文字幕在线免费| 精品美女被调教视频大全网站| 秋霞精品一区二区三区| 国产精品每日更新在线播放网址| 佐山爱在线视频| 欧美日本一区| 欧美日韩综合久久| 精品久久亚洲| 国产精品久久久久9999| xxx性欧美| 日韩在线www| 日本一区高清| 日韩欧美成人激情| 无码人妻黑人中文字幕| 亚洲午夜电影在线| 来吧亚洲综合网| 粉嫩av一区二区三区在线播放| 欧美 日韩 国产一区| 亚洲激情中文在线| 久久国产欧美精品| 99精品在免费线中文字幕网站一区| 国产精品色婷婷视频| 在线高清av| 国产69精品久久久| a黄色片在线观看| 中文字幕精品一区久久久久| 三级网站在线看| 日韩欧美成人免费视频| 丰满的亚洲女人毛茸茸| 久久久久久久久蜜桃| 国产一级免费片| 国产裸体歌舞团一区二区| 韩国中文字幕av| 老牛影视一区二区三区| 1024av视频| 一本色道久久| 黄色片网址在线观看| 亚洲午夜伦理| 亚洲啪啪av| 免费成人av| 91视频8mav| 国产精品xnxxcom| 成人免费观看网址| 亚洲欧美在线人成swag| 国产日韩在线看片| 99久久久国产精品免费调教网站| 91极品视频在线| 大黄网站在线观看| 国产69精品久久久久99| 操喷在线视频| 欧美激情18p| 97人人爽人人澡人人精品| 久久久久久九九九| av手机免费在线观看| 久久久爽爽爽美女图片| 黄色av网站在线播放| 伊人伊成久久人综合网站| 国产高清av在线| 色诱女教师一区二区三区| 成年人免费在线视频| 国产一区二区欧美日韩| av在线免费一区| 色噜噜狠狠狠综合曰曰曰| 在线看黄色av| 久久国产精品免费视频| 欧美xxxx免费虐| 韩国三级日本三级少妇99| 欧美hdxxx| 韩国欧美亚洲国产| 中国色在线日|韩| 国产精品扒开腿做| 欧美videos粗暴| 91精品网站| 果冻天美麻豆一区二区国产| 免费精品视频一区二区三区| 欧美亚洲激情| 2022中文字幕| 亚洲资源av| 色七七在线观看| 激情久久久久久久久久久久久久久久| 午夜大片在线观看| 国产精品资源在线看| 国产精品熟妇一区二区三区四区| 97久久超碰国产精品| 天天干天天舔天天操| 亚洲欧洲成人精品av97| 国产一级理论片| 天天色综合天天| 日本中文字幕久久| 欧美高清性hdvideosex| 内射无码专区久久亚洲| 伊人伊成久久人综合网站| av网址在线| 久久久久亚洲精品国产| 欧美黄色网页| 97久草视频| 西瓜成人精品人成网站| 一本色道久久99精品综合| 亚洲网址在线| 青青草原国产在线视频| 成人va在线观看| 国产精品国产三级国产传播| 亚洲午夜日本在线观看| 中文字幕第31页| 精品成人佐山爱一区二区| 国产乱视频在线观看| 欧美激情精品久久久| 高清av一区二区三区| 97自拍视频| 日韩一区三区| 国产精品丝袜久久久久久消防器材| 日韩av中文在线观看| 亚洲久久久久久| 亚洲三级在线播放| 老熟妇仑乱一区二区av| 日韩欧美你懂的| 色网站在线看| 国产成人综合一区二区三区| 北条麻妃在线一区二区免费播放 | 国内精品二区| 一区二区三区中文| 久久精品免费网站| 福利一区二区在线观看| 国产精品国产三级国产传播| 精品成人av一区| 亚洲产国偷v产偷v自拍涩爱| 日韩在线国产精品| 黄色羞羞视频在线观看| 亚洲自拍小视频| 欧美亚洲在线日韩| 国产又黄又大又粗视频| 国产自产v一区二区三区c| 精品人妻中文无码av在线| 一区二区免费视频| 99在线观看免费| 日韩视频免费在线| 久久国内精品| 日本一区二区三区视频在线播放| 亚洲色诱最新| 看全色黄大色黄女片18| 一区二区三区在线看| 这里只有久久精品视频| 亚洲欧美综合v| 日本一本在线免费福利| 成人淫片在线看| 欧美少妇性xxxx| 日日碰狠狠躁久久躁婷婷| 91麻豆国产自产在线观看| 奇米影视第四色777| 亚洲电影免费观看高清完整版在线| av网站网址在线观看| 91免费精品国偷自产在线| 久久国产精品成人免费观看的软件| 久久久久久三级| 国产精品三级av在线播放| 亚洲视频一区在线播放| 色系列之999| 91精品在线免费视频| 熟女熟妇伦久久影院毛片一区二区| 久久99精品久久久| 国产精品99久久久久久成人| 91精品国产色综合久久ai换脸| 中文字幕在线播放网址| 成人欧美一区二区三区在线观看| 大色综合视频网站在线播放| 日本中文字幕影院| 亚洲丝袜自拍清纯另类| 精品人妻伦一区二区三区久久| 欧美肥臀大乳一区二区免费视频| 91精品尤物| 很污的网站在线观看| 91老司机福利 在线| 无码人妻精品一区二区三区不卡| 亚洲精品在线一区二区| www在线免费观看视频| 国产欧美日韩一区二区三区| 999亚洲国产精| 国产肥白大熟妇bbbb视频| 欧美日韩一级二级三级| 欧美三级黄网| 99久久99| 久久男女视频| 色婷婷国产精品免| 51精品秘密在线观看| 91高清视频在线观看| 秋霞久久久久久一区二区| 日日夜夜精品视频天天综合网| 极品尤物一区二区| 欧美www视频| 亚洲www.| 999一区二区三区| 国产亚洲一二三区| 国产精品女人久久久| 日韩资源在线观看| 91午夜精品| 少妇一级淫免费播放| 亚洲精品第1页| 久久久久久青草| 亚洲中国色老太| 老司机精品久久| 国产精品成人国产乱| 亚洲日本中文字幕免费在线不卡| 电影91久久久| 国产精品免费入口| 亚洲色图在线视频| 精品亚洲综合| av电影成人| 精品一区二区日韩| 久久99国产综合精品免费|