java反射模式_Java反射机制详解
對(duì)于一般的開發(fā)者,很少需要直接使用Java反射機(jī)制來完成功能開發(fā),但是反射是很多框架譬如 Spring, Mybatis 實(shí)現(xiàn)的核心,反射雖小,能量卻很大。
本文主要介紹反射相關(guān)的概念以及API的使用,關(guān)于反射的應(yīng)用將在下一篇文章中介紹
反射的介紹
反射(Reflection) 是 Java 在運(yùn)行時(shí)(Run time)可以訪問、檢測(cè)和修改它本身狀態(tài)或行為的一種能力,它允許運(yùn)行中的 Java 程序獲取自身的信息,并且可以操作類或?qū)ο蟮膬?nèi)部屬性。
Class 類介紹:Java虛擬機(jī)為每個(gè)類型管理一個(gè)Class對(duì)象,包含了與類有關(guān)的信息,當(dāng)通過 javac 編譯Java類文件時(shí),生成的同名 .class 文件保存著該類的 Class 對(duì)象,JVM 加載一個(gè)類即是加載該 .class 文件。
Class 和 java.lang.reflect 一起對(duì)反射提供了支持,java.lang.reflect 包中最常用的幾個(gè)類的關(guān)系如下:
其中最主要的三個(gè)類 Field、Method 和 Constructor 分別用于描述類的域、方法和構(gòu)造器,它們有一個(gè)共同的父類 AccessibleObject,它提供了訪問控制檢查的功能。
Field :描述類的域(屬性),可以使用 get() 和 set() 方法讀取和修改 Field 對(duì)象關(guān)聯(lián)的字段;
Method :描述類的方法,可以使用 invoke() 方法調(diào)用與 Method 對(duì)象關(guān)聯(lián)的方法;
Constructor :描述類的構(gòu)造器,可以用 Constructor 創(chuàng)建新的對(duì)象。
下面將通過幾個(gè)程序來學(xué)習(xí)Java反射機(jī)制。
準(zhǔn)備兩個(gè)類用于實(shí)驗(yàn)
我們特別定義兩個(gè)類,Person和Employee,其中Employee繼承自Person,且各自都有一個(gè)private,protected,public修飾的域(屬性),Employee還有private,public修飾的方法
public class Person {
public String name; // 姓名 公有
protected String age; // 年齡 保護(hù)
private String hobby; // 愛好 私有
public Person(String name, String age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
public String getHobby() {
return hobby;
}
}
public class Employee extends Person {
public static Integer totalNum = 0; // 員工數(shù)
public int empNo; // 員工編號(hào) 公有
protected String position; // 職位 保護(hù)
private int salary; // 工資 私有
public void sayHello() {
System.out.println(String.format("Hello, 我是 %s, 今年 %s 歲, 愛好是%s, 我目前的工作是%s, 月入%s元\n", name, age, getHobby(), position, salary));
}
private void work() {
System.out.println(String.format("My name is %s, 工作中勿擾.", name));
}
public Employee(String name, String age, String hobby, int empNo, String position, int salary) {
super(name, age, hobby);
this.empNo = empNo;
this.position = position;
this.salary = salary;
Employee.totalNum++;
}
}
獲取 Class 對(duì)象
獲取 Class 對(duì)象的方式有三種:使用 Class 類的 forName 靜態(tài)方法;直接獲取某一個(gè)對(duì)象的 class;調(diào)用某個(gè)對(duì)象的 getClass() 方法
public class ClassTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class c1 = Class.forName("reflect.Employee"); // 第1種,forName 方式獲取Class對(duì)象
Class c2 = Employee.class; // 第2種,直接通過類獲取Class對(duì)象
Employee employee = new Employee("小明", "18", "寫代碼", 1, "Java攻城獅", 100000);
Class c3 = employee.getClass(); // 第3種,通過調(diào)用對(duì)象的getClass()方法獲取Class對(duì)象
if (c1 == c2 && c1 == c3) { // 可以通過 == 比較Class對(duì)象是否為同一個(gè)對(duì)象
System.out.println("c1、c2、c3 為同一個(gè)對(duì)象");
System.out.println(c1); // class reflect.Employee
}
}
}
通過反射來創(chuàng)建實(shí)例
通過反射來生成對(duì)象主要有兩種方式
使用Class對(duì)象的newInstance()方法來創(chuàng)建Class對(duì)象對(duì)應(yīng)類的實(shí)例
先通過Class對(duì)象獲取指定的Constructor對(duì)象,再調(diào)用Constructor對(duì)象的newInstance()方法來創(chuàng)建實(shí)例
public class NewInstanceTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class c = Date.class;
Date date1 = (Date) c.newInstance(); // 第1種方式:使用Class對(duì)象的newInstance()方法來創(chuàng)建Class對(duì)象對(duì)應(yīng)類的實(shí)例
System.out.println(date1); // Wed Dec 19 22:57:16 CST 2018
long timestamp =date1.getTime();
Constructor constructor = c.getConstructor(long.class);
Date date2 = (Date)constructor.newInstance(timestamp); // 第2種方式:先通過Class對(duì)象獲取指定的Constructor對(duì)象,再調(diào)用Constructor對(duì)象的newInstance()方法來創(chuàng)建實(shí)例
System.out.println(date2); // Wed Dec 19 22:57:16 CST 2018
}
}
獲取類的全部信息
上面我們定義了兩個(gè)類,現(xiàn)在有個(gè)需求:獲取Employee的類名,構(gòu)造器簽名,所有的方法,所有的域(屬性)和值,然后打印出來。該通過什么方式來實(shí)現(xiàn)呢?
沒錯(cuò),猜對(duì)了,就是通過反射來獲取這些類的信息,在上面介紹中我們知道JVM虛擬機(jī)為每個(gè)類型管理一個(gè)Class對(duì)象,
為了完成我們的需求,我們需要知道一些API如下:
獲取類信息的部分API
String getName() 獲取這個(gè)Class的類名
Constructor[] getDeclaredConstructors() 返回這個(gè)類的所有構(gòu)造器的對(duì)象數(shù)組,包含保護(hù)和私有的構(gòu)造器;相近的方法 getConstructors() 則返回這個(gè)類的所有公有構(gòu)造器的對(duì)象數(shù)組,不包含保護(hù)和私有的構(gòu)造器
Method[] getDeclaredMethods() 返回這個(gè)類或接口的所有方法,包括保護(hù)和私有的方法,不包括超類的方法;相近的方法 getMethods() 則返回這個(gè)類及其超類的公有方法的對(duì)象數(shù)組,不含保護(hù)和私有的方法
Field[] getDeclaredFields() 返回這個(gè)類的所有域的對(duì)象數(shù)組,包括保護(hù)域和私有域,不包括超類的域;還有一個(gè)相近的API getFields(),返回這個(gè)類及其超類的公有域的對(duì)象數(shù)組,不含保護(hù)域和私有域
int getModifiers() 返回一個(gè)用于描述Field、Method和Constructor的修飾符的整形數(shù)值,該數(shù)值代表的含義可通過Modifier這個(gè)類分析
Modifier 類 它提供了有關(guān)Field、Method和Constructor等的訪問修飾符的信息,主要的方法有:toString(int modifiers)返回整形數(shù)值modifiers代表的修飾符的字符串;isAbstract是否被abstract修飾;isVolatile是否被volatile修飾;isPrivate是否為private;isProtected是否為protected;isPublic是否為public;isStatic是否為static修飾;等等,見名知義
打印類信息程序
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
String name;
if (args.length > 0) {
name = args[0];
} else {
Scanner in = new Scanner(System.in);
System.out.println("輸入一個(gè)類名(e.g. java.util.Date):"); // reflect.Employee
name = in.next();
}
try {
Class cl = Class.forName(name);
Class superCl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print("class " + name);
if (superCl != null && superCl != Object.class) {
System.out.print(" extends " + superCl.getName());
}
System.out.println("\n{");
printConstructors(cl); // 打印構(gòu)造方法
System.out.println();
printMethods(cl); // 打印方法
System.out.println();
printFields(cl); // 打印屬性
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.exit(0);
}
/**
* 打印Class對(duì)象的所有構(gòu)造方法
*/
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c : constructors) {
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(name + "(");
// 打印構(gòu)造參數(shù)
Class[] paramTypes = c.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
/**
* 打印Class的所有方法
*/
public static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods();
//Method[] methods = cl.getMethods();
for (Method m : methods) {
Class retType = m.getReturnType(); // 返回類型
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(retType.getName() + " " + m.getName() + "(");
Class[] paramTypes = m.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
/**
* 打印Class的所有屬性
*/
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for (Field f: fields) {
Class type = f.getType();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.length()> 0) {
System.out.print(modifiers + " ");
}
System.out.println(type.getName() + " " + f.getName() + ";");
}
}
}
運(yùn)行程序,然后在控制臺(tái)輸入一個(gè)我們想分析的類的全名,譬如 reflect.Employee,可得到下面的輸出
輸入一個(gè)類名(e.g. java.util.Date):
reflect.Employee
public class reflect.Employee extends reflect.Person
{
private reflect.Employee(java.lang.String, java.lang.String, java.lang.String);
public reflect.Employee(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, int);
public static void main([Ljava.lang.String;);
public void sayHello();
private void work();
public static java.lang.Integer totalNum;
public int empNo;
protected java.lang.String position;
private int salary;
}
上面的輸出中我們得到的類的構(gòu)造器,所有方法和所有的域(屬性),包括修飾符,名稱和參數(shù)類型都是準(zhǔn)確的,看來反射機(jī)制能完成我們的需求。
小結(jié)一下,我們通過 getDeclaredConstructors() 獲取構(gòu)造器信息,通過 getDeclaredMethods() 獲得方法信息,通過 getDeclaredFields() 獲得域信息,再通過 getModifiers() 和 Modifier類 獲得修飾符信息,匯總起來就得到了整個(gè)類的類信息。
運(yùn)行時(shí)查看對(duì)象數(shù)據(jù)域的實(shí)際內(nèi)容
上面我們已經(jīng)獲取到了類的信息,現(xiàn)在又有一個(gè)需求:在運(yùn)行時(shí)查看對(duì)象的數(shù)據(jù)域的實(shí)際值。這個(gè)場(chǎng)景就像我們通過IDEA調(diào)試程序,設(shè)置斷點(diǎn)攔截到程序后,查看某個(gè)對(duì)象的屬性的值。
我們知道java反射機(jī)制提供了查看類信息的API,那么它應(yīng)該也提供了查看Field域?qū)嶋H值和設(shè)置Field域?qū)嶋H值的API,沒錯(cuò),猜對(duì)了,確實(shí)有相關(guān)的API,但是有個(gè)疑問,有一些屬性是private修飾的私有域,這種是否也能直接查看和設(shè)置呢?看完下面的API即可知道答案
運(yùn)行時(shí)查看對(duì)象數(shù)據(jù)域?qū)嶋H內(nèi)容的相關(guān)API
Class> getComponentType() 返回?cái)?shù)組類里組件類型的 Class,如果不是數(shù)組類則返回null
boolean isArray() 返回這個(gè)類是否為數(shù)組,同類型的API還有 isAnnotation、isAsciiDigit、isEnum、isInstance、isInterface、isLocalClass、isPrimitive 等
int Array.getLength(obj) 返回?cái)?shù)組對(duì)象obj的長(zhǎng)度
Object Array.get(obj, i) 獲取數(shù)組對(duì)象下標(biāo)為i的元素
boolean isPrimitive() 返回這個(gè)類是否為8種基本類型之一,即是否為boolean, byte, char, short, int, long, float, 和double 等原始類型
Field getField(String name) 獲取指定名稱的域?qū)ο?/p>
AccessibleObject.setAccessible(fields, true) 當(dāng)訪問 Field、Method 和 Constructor 的時(shí)候Java會(huì)執(zhí)行訪問檢查,如果訪問者沒有權(quán)限將拋出SecurityException,譬如訪問者是無法訪問private修飾的域的。通過設(shè)置 setAccessible(true) 可以取消Java的執(zhí)行訪問檢查,這樣訪問者就獲得了指定 Field、Method 或 Constructor 訪問權(quán)限
Class> Field.getType() 返回一個(gè)Class 對(duì)象,它標(biāo)識(shí)了此 Field 對(duì)象所表示字段的聲明類型
Object Field.get(Object obj) 獲取obj對(duì)象上當(dāng)前域?qū)ο蟊硎镜膶傩缘膶?shí)際值,獲取到的是一個(gè)Object對(duì)象,實(shí)際使用中還需要轉(zhuǎn)換成實(shí)際的類型,或者可以通過 getByte()、getChar、getInt() 等直接獲取具體類型的值
void Field.set(Object obj, Object value) 設(shè)置obj對(duì)象上當(dāng)前域表示的屬性的實(shí)際值
查看對(duì)象數(shù)據(jù)域?qū)嶋H內(nèi)容程序
了解完上述相關(guān)API之后,我們敲出下面的程序來驗(yàn)證
public class ObjectAnalyzer {
private ArrayList visited = new ArrayList<>();
public String toString(Object obj) {
if (obj == null) {
return "null";
}
if (visited.contains(obj)) { // 如果該對(duì)象已經(jīng)處理過,則不再處理
return "...";
}
visited.add(obj);
Class cl = obj.getClass(); // 獲取Class對(duì)象
if (cl == String.class) { // 如果是String類型則直接轉(zhuǎn)為String
return (String) obj;
}
if (cl.isArray()) { // 如果是數(shù)組
String r = cl.getComponentType() + "[]{\n"; // 數(shù)組的元素的類型
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) { // 不是數(shù)組的第一個(gè)元素加逗號(hào)和換行,顯示更加美觀
r += ",\n";
}
r += "\t";
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) { // Class為8種基本類型的時(shí)候?yàn)?true,直接輸出
r += val;
} else {
r += toString(val); // 不是8中基本類型時(shí),說明是類,遞歸調(diào)用toString
}
}
return r + "\n}";
}
// 既不是String,也不是數(shù)組時(shí),輸出該對(duì)象的類型和屬性值
String r = cl.getName();
do {
r += "[";
Field[] fields = cl.getDeclaredFields(); // 獲取該類自己定義的所有域,包括私有的,不包括父類的
AccessibleObject.setAccessible(fields, true); // 訪問私有的屬性,需要打開這個(gè)設(shè)置,否則會(huì)報(bào)非法訪問異常
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) { // 通過 Modifier 可獲取該域的修飾符,這里判斷是否為 static
if (!r.endsWith("[")) {
r += ",";
}
r += f.getName() + "="; // 域名稱
try {
Class t = f.getType(); // 域(屬性)的類型
Object val = f.get(obj); // 獲取obj對(duì)象上該域的實(shí)際值
if (t.isPrimitive()) { // 如果類型為8種基本類型,則直接輸出
r += val;
} else {
r += toString(val); // 不是8種基本類型,遞歸調(diào)用toString
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass(); // 繼續(xù)打印超類的類信息
} while (cl != null);
return r;
}
}
測(cè)試驗(yàn)證結(jié)果
接下來驗(yàn)證一下獲取數(shù)據(jù)域?qū)嶋H值是否正確,分別打印數(shù)組、自定義類的對(duì)象的實(shí)際值
public class ObjectAnalyzerTest {
public static void main(String[] args) {
int size = 4;
ArrayList squares = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
squares.add(i * i);
}
ObjectAnalyzer objectAnalyzer = new ObjectAnalyzer(); // 創(chuàng)建一個(gè)上面定義的分析類ObjectAnalyzer的對(duì)象
System.out.println(objectAnalyzer.toString(squares)); // 分析ArrayList對(duì)象的實(shí)際值
Employee employee = new Employee("小明", "18", "愛好寫代碼", 1, "Java攻城獅", 100); // 分析自定義類Employee的對(duì)象的實(shí)際值
System.out.println(objectAnalyzer.toString(employee));
}
}
輸出如下
java.util.ArrayList[elementData=class java.lang.Object[]{
java.lang.Integer[value=0][][],
java.lang.Integer[value=1][][],
java.lang.Integer[value=4][][],
java.lang.Integer[value=9][][]
},size=4][modCount=4][][]
reflect.Employee[empNo=1,position=Java攻城獅,salary=100][name=小明,age=18,hobby=愛好寫代碼][]
其中ArrayList打印了類名和5個(gè)元素的類型和值,Employee 打印了類名,自己定義的3個(gè)基本類型的屬性的實(shí)際值,和父類Person的3個(gè)基本類型的屬性的實(shí)際值
需要注意的是,position,age 是 protected 保護(hù)域,salary,hobby 是 private 私有域,Java的安全機(jī)制只允許查看任意對(duì)象有哪些域,但是不允許讀取它們的值
程序中是通過 AccessibleObject.setAccessible(fields, true) 將域設(shè)置為了可訪問,取消了Java的執(zhí)行訪問檢查,因此可以訪問,如果不加會(huì)報(bào)異常 IllegalAccessException
小結(jié)一下,我們通過 setAccessible(true) 繞過了Java執(zhí)行訪問檢查,因此能夠訪問私有域,通過 Field.getType() 獲得了屬性的聲明類型,通過了 Field.get(Object obj) 獲得了該域?qū)傩缘膶?shí)際值,還有一個(gè)沒用上的 Field.set(Object obj, Object value) 設(shè)置域?qū)傩缘膶?shí)際值
調(diào)用任意方法
上面我們已經(jīng)獲取了類的構(gòu)造器,方法,域,查看和設(shè)置了域的實(shí)際值,那么是不是還可以在調(diào)用對(duì)象的方法呢?嘿嘿,又猜對(duì)了,機(jī)智,類的方法信息,獲取都獲取了,當(dāng)然就要調(diào)用一下,來都來了
上面查看Field的實(shí)際值是通過 Field 類的 get() 方法,與之類似,Method 調(diào)用方法是通過 Method 類的 invoke 方法
調(diào)用任意方法相關(guān)的API
Method getMethod(String name, Class>... parameterTypes) 獲取指定的 Method,參數(shù) name 為要獲取的方法名,parameterTypes 為指定方法的參數(shù)的 Class,由于可能存在多個(gè)同名的重載方法,所以只有提供正確的 parameterTypes 才能準(zhǔn)確的獲取到指定的 Method
Object invoke(Object obj, Object... args) 執(zhí)行方法,第一個(gè)參數(shù)執(zhí)行該方法的對(duì)象,如果是static修飾的類方法,則傳null即可;后面是傳給該方法執(zhí)行的具體的參數(shù)值
調(diào)用任意方法程序
public class MethodTableTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Employee employee = new Employee("小明", "18", "寫代碼", 1, "Java攻城獅", 100000);
Method sayHello = employee.getClass().getMethod("sayHello");
System.out.println(sayHello); // 打印 sayHello 的方法信息
sayHello.invoke(employee); // 讓 employee 執(zhí)行 sayHello 方法
double x = 3.0;
Method square = MethodTableTest.class.getMethod("square", double.class); // 獲取 MethodTableTest 的square方法
double y1 = (double) square.invoke(null, x); // 調(diào)用類方法 square 求平方,方法參數(shù) x
System.out.printf("square %-10.4f -> %10.4f%n", x, y1);
Method sqrt = Math.class.getMethod("sqrt", double.class); // 獲取 Math 的 sqrt 方法
double y2 = (double) sqrt.invoke(null, x); // 調(diào)用類方法 sqrt 求根,方法參數(shù) x
System.out.printf("sqrt %-10.4f -> %10.4f%n", x, y2);
}
// static靜態(tài)方法 計(jì)算乘方
public static double square(double x) {
return x * x;
}
}
執(zhí)行結(jié)果
public void reflect.Employee.sayHello()
Hello, 我是 小明, 今年 18 歲, 愛好是寫代碼, 我目前的工作是Java攻城獅, 月入100000元
square 3.0000 -> 9.0000
sqrt 3.0000 -> 1.7321
相信大家都看懂啦,通過 getMethod() 獲取指定的 Method,再調(diào)用 Method.invoke() 執(zhí)行該方法
反射的優(yōu)缺點(diǎn)
此段引用自 CyC2018/CS-Notes
反射的優(yōu)點(diǎn):
可擴(kuò)展性 :應(yīng)用程序可以利用全限定名創(chuàng)建可擴(kuò)展對(duì)象的實(shí)例,來使用來自外部的用戶自定義類。
類瀏覽器和可視化開發(fā)環(huán)境 :一個(gè)類瀏覽器需要可以枚舉類的成員??梢暬_發(fā)環(huán)境(如 IDE)可以從利用反射中可用的類型信息中受益,以幫助程序員編寫正確的代碼。
調(diào)試器和測(cè)試工具 : 調(diào)試器需要能夠檢查一個(gè)類里的私有成員。測(cè)試工具可以利用反射來自動(dòng)地調(diào)用類里定義的可被發(fā)現(xiàn)的 API 定義,以確保一組測(cè)試中有較高的代碼覆蓋率。
反射的缺點(diǎn):
盡管反射非常強(qiáng)大,但也不能濫用。如果一個(gè)功能可以不用反射完成,那么最好就不用。在我們使用反射技術(shù)時(shí),下面幾條內(nèi)容應(yīng)該牢記于心。
性能開銷 :反射涉及了動(dòng)態(tài)類型的解析,所以 JVM 無法對(duì)這些代碼進(jìn)行優(yōu)化。因此,反射操作的效率要比那些非反射操作低得多。我們應(yīng)該避免在經(jīng)常被執(zhí)行的代碼或?qū)π阅芤蠛芨叩某绦蛑惺褂梅瓷洹?/p>
安全限制 :使用反射技術(shù)要求程序必須在一個(gè)沒有安全限制的環(huán)境中運(yùn)行。如果一個(gè)程序必須在有安全限制的環(huán)境中運(yùn)行,如 Applet,那么這就是個(gè)問題了。
內(nèi)部暴露 :由于反射允許代碼執(zhí)行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會(huì)導(dǎo)致意料之外的副作用,這可能導(dǎo)致代碼功能失調(diào)并破壞可移植性。反射代碼破壞了抽象性,因此當(dāng)平臺(tái)發(fā)生改變的時(shí)候,代碼的行為就有可能也隨著變化。
后記
歡迎評(píng)論、轉(zhuǎn)發(fā)、分享,您的支持是我最大的動(dòng)力
關(guān)注【小旋鋒】微信公眾號(hào),及時(shí)接收博文推送
總結(jié)
以上是生活随笔為你收集整理的java反射模式_Java反射机制详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python uwsgi_Python
- 下一篇: 在JAVA语言程序中main_在Java