中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

深圳金融投資網(wǎng)站建設(shè)bt最佳磁力搜索引擎

深圳金融投資網(wǎng)站建設(shè),bt最佳磁力搜索引擎,網(wǎng)站網(wǎng)頁切換怎么做,百度搜索網(wǎng)站怎么做手寫實現(xiàn)一個動態(tài)代理框架 什么是代理模式什么是動態(tài)代理動態(tài)代理中的編譯、類加載與對象實例化手寫實現(xiàn)一個動態(tài)代理框架實現(xiàn)細節(jié)DynamicProxyHandlerProxy生成代碼寫入代碼到磁盤文件調(diào)用編譯器進行編譯調(diào)用類加載器進行類加載反射實例化刪除前面生成的java文件和class文件 C…

手寫實現(xiàn)一個動態(tài)代理框架

  • 什么是代理模式
  • 什么是動態(tài)代理
  • 動態(tài)代理中的編譯、類加載與對象實例化
  • 手寫實現(xiàn)一個動態(tài)代理框架
    • 實現(xiàn)細節(jié)
      • DynamicProxyHandler
      • Proxy
        • 生成代碼
        • 寫入代碼到磁盤文件
        • 調(diào)用編譯器進行編譯
        • 調(diào)用類加載器進行類加載
        • 反射實例化
        • 刪除前面生成的java文件和class文件
      • Coder
    • 來玩一把
  • JDK動態(tài)代理和我們的區(qū)別

最近寫了一個動態(tài)代理框架,在這里分享一下,順便分析一下動態(tài)代理的原理,當(dāng)做復(fù)習(xí)。

在這里插入圖片描述

什么是代理模式

首先我們來了解一下代理模式,代理模式是二十三種設(shè)計模式中的其中一種,用于對目標對象進行代理增強。

在這里插入圖片描述

代理類通常和目標類實現(xiàn)同一個接口,這樣我們就可以用一個接口類型變量去引用一個代理類對象,然后又可以當(dāng)成目標對象去使用。

在這里插入圖片描述

public interface Handler {void handle();
}
public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target = target;}...
}
public class Target implements Handler {...
}
public class Main {public static void main(String[] args) {Target target = new Target();Handler handler = new Proxy(target);...}
}

當(dāng)我們對目標對象做了代理之后,就可以在調(diào)用目標對象方法的前后添加一些增強的處理邏輯。


public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target = target;}@Overridepublic void handle() {pre(); // 前置增強邏輯target.handle();post(); // 后置增強邏輯}
}public class Target implements Handler {@Overridepublic void handle() {...}
}

在這里插入圖片描述

比如我們要在目標方法的前后添加日志打印,那么我們可以通過代理模式實現(xiàn)。

public class Proxy implements Handler {private Handler target;@Overridepublic void handle() {log.info(...);target.handle();log info(...);}}

這樣做的好處有兩點:

  • 我們可以為目標類添加額外的處理邏輯,而不需要改動目標類原有的代碼。
  • 我們可以更換目標對象,但是代理類中的增強處理邏輯不用變,更換后的目標對象依然可以復(fù)用原先的增強邏輯。

什么是動態(tài)代理

動態(tài)代理是對普通代理模式的優(yōu)化,普通代理模式有一個缺點,那就是我們需要手動編寫代理邏輯。

比如我們的代理邏輯就是在目標方法的前后添加日志打印。如果只有幾個接口需要代理,那么我們可以手動編寫這幾個接口的代理實現(xiàn)類,那是沒有問題的。

public interface HandlerA {void method1();void method2();void method3();}
public interface HandlerB {void method1();void method2();
}
public interface HandlerC {void method1();void method2();void method3();void method4();
}public class ProxyA implements HandlerA {private HandlerA target;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic void method2() {log.info(...);target.method2();log info(...);}@Overridepublic void method3() {log.info(...);target.method3();log info(...);}}public class ProxyB implements HandlerB {private HandlerB target;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic void method2() {log.info(...);target.method2();log info(...);}}
public class ProxyC implements HandlerC {...
}

但是我們發(fā)現(xiàn),雖然我們編寫的代理類實現(xiàn)了不同的接口,重寫了不同的接口方法,但是前后添加的增強邏輯其實是一模一樣的。而且如果接口比較多的話,我們一個個手寫代理實現(xiàn)類也挺麻煩的。

在這里插入圖片描述

于是就有了動態(tài)代理,動態(tài)代理的主要作用就是動態(tài)生成代理實現(xiàn)類,不需要我們手動編寫。我們只需要定義好增強邏輯,以及需要代理的接口和接口方法,我們就可以通過動態(tài)代理框架生成代理類并實例化代理對象。

在這里插入圖片描述

比如JDK的動態(tài)代理,我們定義好我們的接口比如Handler,然后實現(xiàn)JDK動態(tài)代理提供的接口InvocationHandler在里面編寫增強邏輯,就可以作為輸入?yún)?shù)調(diào)用JDK動態(tài)代理提供的工具類Proxy生成代理實現(xiàn)類并反射實例化代理對象。

在這里插入圖片描述

這樣就不需要我們自己編寫那么多的代理實現(xiàn)類了,省掉了一大波繁瑣的工作,一片歲月靜好。

在這里插入圖片描述

動態(tài)代理中的編譯、類加載與對象實例化

我們自己手動編寫代理類時,從代理類代碼的編寫到代理對象的創(chuàng)建,整個流程是這樣子:

在這里插入圖片描述

代碼編寫和編譯器編譯是靜態(tài)的,因為是在JVM啟動之前提前做好的。

動態(tài)代理相當(dāng)于是就是在JVM運行的時候,由動態(tài)代理框架根據(jù)我們給定的接口和代理邏輯動態(tài)編寫代理實現(xiàn)類,然后調(diào)用編譯器進行動態(tài)編譯,通過類加載器加載到內(nèi)存中,然后反射實例化。

在這里插入圖片描述

在動態(tài)代理的情況下,代碼是在JVM運行時動態(tài)生成的,比如用StringBuilder拼接(也可以是別的方式生成),然后通過調(diào)用JDK提供的API進行運行時編譯,類加載器加載也是通過調(diào)用JDK提供的API進行動態(tài)的加載。也就是說從代碼生成、編譯、類加載、到反射實例化的這一切,都是在JVM已經(jīng)運行的情況下,通過Java代碼動態(tài)實現(xiàn)的,因此稱為動態(tài)代理。

手寫實現(xiàn)一個動態(tài)代理框架

那么,我們就來實現(xiàn)一個我們自己的動態(tài)代理框架,非常簡單,三個類:Coder、DynamicProxyHandler、Proxy

  • Coder:代理類代碼的生成器類,接收一個Class<?>類型的參數(shù)interfaceClass,表示代理類要實現(xiàn)interfaceClass這個接口,Coder根據(jù)這個接口生成代理類的代碼
  • DynamicProxyHandler:留給用戶實現(xiàn)的處理器接口,用戶需要實現(xiàn)DynamicProxyHandler接口并重新invoke以聲明動態(tài)代理的增強邏輯
  • Proxy:生成代理對象的工具類,調(diào)用Coder生成代理類的代碼, 通過編譯器動態(tài)編譯,然后通過類加載器動態(tài)加載編譯出來的代理類class,最后通過反射創(chuàng)建代理對象返回給用戶

在這里插入圖片描述

實現(xiàn)細節(jié)

DynamicProxyHandler

DynamicProxyHandler就是預(yù)留給用戶實現(xiàn)的用于編寫增強邏輯的接口,用戶需要實現(xiàn)DynamicProxyHandler接口,并重寫invoke方法,在invoke方法中編寫增加邏輯。

/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public interface DynamicProxyHandler {Object invoke(Object proxy, Method method, Object[] args);}

invoke方法接收三個參數(shù),proxy是代理對象本身,method是當(dāng)前正在調(diào)用的方法的Method對象,args是當(dāng)前方法接收的參數(shù)。

Proxy

Proxy中的代碼稍稍有點復(fù)雜,不用馬上全部看完,先有個大概印象,下面一步步分析里面干了些啥。

/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public class Proxy {private static boolean enablePrintCode = false;public static void printCode() {enablePrintCode = true;}public static <T> T  newInstance(ClassLoader classLoader, Class<?> interfaceClass, DynamicProxyHandler handler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {String code = Coder.generateCode(interfaceClass);if (enablePrintCode) {System.out.println(code);}String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";path = path.replace('\\', '/');File file = new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter = new FileWriter(file);fileWriter.write(code);fileWriter.close();JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, null, path);String parentPath = path.substring(0, path.lastIndexOf("/"));URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};URLClassLoader loader = new URLClassLoader(urls);Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");Constructor constructor = c.getConstructor(DynamicProxyHandler.class);Object o = constructor.newInstance(handler);new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();file.delete();return (T) o;}}
生成代碼

首先調(diào)用Coder生成代理類的代碼,接收一個接口類型參數(shù)interfaceClass,返回生成的代碼字符串。

        String code = Coder.generateCode(interfaceClass);

Proxy里面有個boolean類型的enablePrintCode屬性,把它設(shè)置為true,會打印動態(tài)生成的代理類的代碼。

        if (enablePrintCode) {System.out.println(code);}
寫入代碼到磁盤文件

然后把生成的代碼寫入到磁盤文件

        String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";path = path.replace('\\', '/');File file = new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter = new FileWriter(file);fileWriter.write(code);fileWriter.close();

磁盤文件的路徑必須在類加載器加載類的目錄下,通過 classLoader.getResource(“”).getPath() 取得類加載器加載類的對應(yīng)目錄。

然后把類的包名中的“.”替換成“/”,創(chuàng)建目錄。

最后創(chuàng)建文件,通過FileWriter把代碼寫入到文件中。

調(diào)用編譯器進行編譯

代碼寫到文件后,就要調(diào)用編譯器把這個java文件編譯成class文件。

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, null, path);

JavaCompiler就是編譯器對象,調(diào)用JavaCompiler的run方法進行編譯,path參數(shù)是需要編譯的java文件的路徑。

調(diào)用類加載器進行類加載

編譯出class文件后,就要調(diào)用類加載器把這個class文件加載到內(nèi)存中,生成一個Class對象。

        String parentPath = path.substring(0, path.lastIndexOf("/"));URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};URLClassLoader loader = new URLClassLoader(urls);Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");

String parentPath = path.substring(0, path.lastIndexOf(“/”)); 這一行是取得java文件所在目錄的路徑,編譯出來的class文件與java文件是同處一個目錄下的,這樣我們就可以找到class文件。

然后就是創(chuàng)建URLClassLoader對象,調(diào)用URLClassLoader的loadClass方法進行類加載,獲得代理類的Class對象。

反射實例化

獲得代理類的Class對象后,就要反射進行實例化。

        Constructor constructor = c.getConstructor(DynamicProxyHandler.class);Object o = constructor.newInstance(handler);

獲得構(gòu)造器對象constructor,調(diào)用構(gòu)造器的newInstance方法進行實例化。

刪除前面生成的java文件和class文件

最后要把生成的文件刪除掉,以免留下垃圾。

        new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();file.delete();

第一行是刪除class文件,第二行是刪除java文件。

Coder

接下來看一下Coder類。這個Coder類不必細看,代碼雖然多,但是都是使用StringBuilder進行字符串拼接,根據(jù)給定的接口拼出一個代理類,沒什么技術(shù)含量。

/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public class Coder {private static Map<String, String> codeCache = new HashMap<>();public static String generateCode(Class<?> interfaceClass) {String interfaceClassName = interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}StringBuilder codeBuilder = new StringBuilder();// packagePackage aPackage = interfaceClass.getPackage();String packageName = aPackage.getName();codeBuilder.append("package ").append(packageName).append(";").append("\n");codeBuilder.append("\n");// importMethod[] methods = interfaceClass.getDeclaredMethods();Set<String> importTypeSet = new HashSet<>();for (Method method : methods) {Class<?> returnType = method.getReturnType();importTypeSet.add(returnType.getName());Class<?>[] parameterTypes = method.getParameterTypes();for (Class<?> parameterType : parameterTypes) {importTypeSet.add(parameterType.getName());}}for (String importType : importTypeSet) {if ("void".equals(importType) ||"long".equals(importType) ||"int".equals(importType) ||"short".equals(importType) ||"byte".equals(importType) ||"char".equals(importType) ||"float".equals(importType) ||"double".equals(importType) ||"boolean".equals(importType)) {continue;}codeBuilder.append("import ").append(importType).append(";").append("\n");}codeBuilder.append("import java.lang.reflect.Method;");codeBuilder.append("import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;");// classcodeBuilder.append("\n\npublic class ").append(interfaceClass.getSimpleName()).append("$$").append(" implements").append(" ").append(interfaceClass.getSimpleName()).append(" {").append("\n\n");// fieldcodeBuilder.append("\tprivate DynamicProxyHandler dynamicProxyHandler;\n\n");// ConstructorcodeBuilder.append("\tpublic ") .append(interfaceClass.getSimpleName()).append("$$").append("(").append("DynamicProxyHandler dynamicProxyHandler) {\n").append("\t\tthis.dynamicProxyHandler = dynamicProxyHandler;").append("\n").append("\t}\n\n");// methodfor (Method method : methods) {Class<?> returnType = method.getReturnType();importTypeSet.add(returnType.getName());codeBuilder.append("\t@Override\n");codeBuilder.append("\t").append("public ").append(returnType.getSimpleName()).append(" ").append(method.getName()).append("(");// method parameterClass<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append(" ").append("var").append(i);if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}codeBuilder.append(") ").append(" {").append("\n");// method bodycodeBuilder.append("\n\t\tMethod method = null;");codeBuilder.append("\n\t\ttry {");codeBuilder.append("\n\t\t\tmethod = Class.forName(").append("\"").append(interfaceClass.getName()).append("\"").append(")").append(".getDeclaredMethod(").append("\"").append(method.getName()).append("\"");if (parameterTypes.length != 0) {codeBuilder.append(", ");for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append(".").append("class");if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}}codeBuilder.append(");");codeBuilder.append("\n\t\t\tmethod.setAccessible(true);");codeBuilder.append("\n\t\t} catch (ClassNotFoundException|NoSuchMethodException e) {");codeBuilder.append("\n\t\t\tthrow new RuntimeException(\"error when generate code\");");codeBuilder.append("\n\t\t}");if (!"void".equals(returnType.getSimpleName())) {codeBuilder.append("\n\t\treturn (").append(returnType.getSimpleName()).append(") ").append("dynamicProxyHandler.invoke(this, method, new Object[]{");} else {codeBuilder.append("dynamicProxyHandler.invoke(this, method, new Object[]{");}for (int i = 0; i < parameterTypes.length; i++) {codeBuilder.append("var").append(i);if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}codeBuilder.append("});");codeBuilder.append("\n").append("\t").append("}");codeBuilder.append("\n\n");}codeBuilder.append("}");String code = codeBuilder.toString();codeCache.put(interfaceClassName, code);return code;}}

Coder 有一個Map<String, String>類型的緩存codeCache,用于緩存曾經(jīng)生成過的代碼,如果下一次再調(diào)用Coder的generateCode,傳入的參數(shù)interfaceClass是同一個接口類型,那么直接取緩存中的結(jié)果返回,不再重復(fù)生成。

        String interfaceClassName = interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}

如果緩存中沒有,就要通過StringBuilder動態(tài)拼接代理類的代碼了。

        StringBuilder codeBuilder = new StringBuilder();...

這個拼接的邏輯就不用細看了,只是繁瑣的工作,只要知道怎么通過Class對象獲取到類信息和方法信息,就可以根據(jù)這些信息拼出一個代理實現(xiàn)類。

  • Package aPackage = interfaceClass.getPackage(); 獲取包路徑。
  • Method[] methods = interfaceClass.getDeclaredMethods(); 獲取接口定義的方法。
  • Class<?> returnType = method.getReturnType(); 獲取方法返回值類型。
  • Class<?>[] parameterTypes = method.getParameterTypes(); 獲取方法參數(shù)類型。

我們看看生成的代理類是怎么樣的。比如我們有一個Hello接口:

/*** @author huangjunyi* @date 2023/11/28 19:58* @desc*/
public interface Hello {String sayHi(int count);}

生成的代理類就是這樣:

package com.huangjunyi1993.simple.dynamic.proxy.test;import java.lang.String;
import java.lang.reflect.Method;import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;public class Hello$$ implements Hello {private DynamicProxyHandler dynamicProxyHandler;public Hello$$(DynamicProxyHandler dynamicProxyHandler) {this.dynamicProxyHandler = dynamicProxyHandler;}@Overridepublic String sayHi(int var0)  {Method method = null;try {method = Class.forName("com.huangjunyi1993.simple.dynamic.proxy.test.Hello").getDeclaredMethod("sayHi", int.class);method.setAccessible(true);} catch (ClassNotFoundException|NoSuchMethodException e) {throw new RuntimeException("error when generate code");}return (String) dynamicProxyHandler.invoke(this, method, new Object[]{var0});}}

生成的代理類的類名是接口名后加兩個“$”,比如這里的接口是Hello,那么生成的代理類就是Hello$$。

public class Hello$$ implements Hello

生成的代理類重寫了給定接口定義的所有方法,方法里面的邏輯都是反射獲取當(dāng)前方法的Method對象,然后調(diào)用DynamicProxyHandler的invoke方法,把當(dāng)前代理對象this、當(dāng)前方法的Method對象method、當(dāng)前方法參數(shù)作為invoke方法的入?yún)ⅰ?/p>

來玩一把

定義一個Hello接口,就是前面的Hello接口:

/*** @author huangjunyi* @date 2023/11/28 19:58* @desc*/
public interface Hello {String sayHi(int count);}

Hello接口實現(xiàn)類HelloImpl,目標類(被代理類):

/*** @author huangjunyi* @date 2023/11/28 20:00* @desc*/
public class HelloImpl implements Hello {@Overridepublic String sayHi(int count) {String result = "";for (int i = 0; i < count; i++) {result += "hi ";}return result;}
}

實現(xiàn)DynamicProxyHandler接口,編寫增強邏輯:

/*** @author huangjunyi* @date 2023/11/28 20:02* @desc*/
public class HelloHandler implements DynamicProxyHandler {private Hello target;public HelloHandler(Hello target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) {System.out.println("HelloHandler invoke");try {Object invoke = method.invoke(target, args);System.out.println(invoke);return invoke;} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}return null;}
}

測試類:

/*** @author huangjunyi* @date 2023/11/24 10:05* @desc*/
public class A {@Testpublic void test() throws Exception {HelloImpl hello = new HelloImpl();HelloHandler helloHandler = new HelloHandler(hello);// 調(diào)用這個方法,可以打印生成的代理類的代碼// Proxy.printCode();Hello o = Proxy.newInstance(hello.getClass().getClassLoader(), Hello.class, helloHandler);o.sayHi(5);}}

運行測試類,控制臺輸出:

HelloHandler invoke
hi hi hi hi hi 

JDK動態(tài)代理和我們的區(qū)別

JDK動態(tài)代理大體流程和我們的思路是相同的,我們看看JDK的Proxy類的newProxyInstance方法:

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{final Class<?>[] intfs = interfaces.clone();// 生成代理類的Class對象Class<?> cl = getProxyClass0(loader, intfs);try {// 獲取構(gòu)造器對象final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;cons.setAccessible(true);// 構(gòu)造器反射實例化return cons.newInstance(new Object[]{h});} catch (...) {...}}

里面我省略了許多雜七雜八的代碼,只留下核心流程,可以看到也是生成一個代理類的Class對象,然后反射調(diào)用它的構(gòu)造器生成代理對象。

getProxyClass0(loader, intfs)最終會調(diào)到ProxyClassFactory的apply方法:

		public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 。。。。。。// 生成的不是字符串,而是二進制byte數(shù)組,class字節(jié)碼文件里面的內(nèi)容byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 調(diào)用native方法直接進行類的初始化,生成Class對象// 沒有寫盤、編譯、類加載的這幾步動作return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {...}}

這里就是和我們區(qū)別最大的地方,JDK動態(tài)代理不是通過字符串拼接生成的代理類代碼,而是生成的byte[]類型,里面的內(nèi)容也不是java文件里面的代碼,而是class字節(jié)碼文件里面的內(nèi)容。

然后直接調(diào)用defineClass0方法(defineClass0方法是native方法)進行類的初始化,省略了寫入磁盤(我們也可以設(shè)置要保存生成的class文件,這樣會把class文件寫到磁盤)、編譯、調(diào)用類加載器進行類加載的這幾步。

在這里插入圖片描述

全文完。

在這里插入圖片描述

http://www.risenshineclean.com/news/52433.html

相關(guān)文章:

  • 一定得做網(wǎng)站認證企業(yè)軟文
  • 快速做網(wǎng)站套餐廣告聯(lián)盟平臺
  • 建筑設(shè)計網(wǎng)站大全網(wǎng)站windows優(yōu)化大師是官方的嗎
  • 千牛網(wǎng)站上的店鋪推廣怎么做福州整站優(yōu)化
  • 程序員源碼網(wǎng)站個人怎么創(chuàng)建網(wǎng)站
  • 國外空間做網(wǎng)站怎么樣百度怎么免費推廣
  • jsp網(wǎng)站建設(shè)美食焊工培訓(xùn)內(nèi)容有哪些
  • 濰坊做網(wǎng)站網(wǎng)站安全
  • 國外有哪些做服裝的網(wǎng)站有哪些快速排序優(yōu)化
  • wordpress上傳.sh腳本寧波seo排名方案優(yōu)化公司
  • 自己做的網(wǎng)站首頁變成符號了天津百度seo推廣
  • 做設(shè)計有哪些好用的素材網(wǎng)站有哪些選擇寧波seo優(yōu)化公司
  • 響應(yīng)式網(wǎng)站建設(shè)案例淘寶數(shù)據(jù)查詢
  • wordpress能做app嗎seo賺錢
  • 網(wǎng)站開發(fā)屬于大學(xué)那個專業(yè)網(wǎng)站制作方案
  • 重慶做商城網(wǎng)站網(wǎng)絡(luò)營銷推廣計劃書
  • 做文案策劃需要看什么網(wǎng)站地推團隊如何收費
  • 南京做企業(yè)網(wǎng)站博客營銷案例
  • 用什么軟件開發(fā)手機appseo百度關(guān)鍵詞優(yōu)化軟件
  • 有域名沒有服務(wù)器怎么做網(wǎng)站seo優(yōu)化是什么職業(yè)
  • 如何做日本語網(wǎng)站百度品牌廣告
  • 網(wǎng)站換模板要怎么做app推廣賺傭金
  • 網(wǎng)站建設(shè)流程簡圖獨立站推廣
  • 馬鞍山做網(wǎng)站公司排名廣州seo公司品牌
  • 做視頻點播網(wǎng)站如何賺錢seo云優(yōu)化方法
  • 科技未來網(wǎng)站建設(shè)東莞百度快照優(yōu)化排名
  • 中國和城鄉(xiāng)建設(shè)部網(wǎng)站首頁提高百度搜索排名工具
  • 蓬萊市住房和規(guī)劃建設(shè)管理局網(wǎng)站北京關(guān)鍵詞快速排名
  • 西昌市規(guī)劃建設(shè)局網(wǎng)站怎樣在百度上發(fā)布作品
  • 實驗教學(xué)網(wǎng)站的建設(shè)研究短視頻seo排名加盟