反射
一、概念
反射 - 镜子 - 获取某个类的相关信息,相关信息有属性(字段)、方法、构造方法、访问修饰符、参数等等。
通过反射可以让程序在运行时动态地做一些相关的操作,读取类的信息以及调用方法。
目的:Java的反射机制就是增加程序的灵活性,避免将程序写死到代码里。
优点:
1)能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
2)与Java动态编译相结合,可以实现无比强大的功能
缺点:
1)使用反射的性能较低
2)使用反射相对来说不安全
3)破坏了类的封装性,可以通过反射获取这个类的私有方法和属性
任何事物,都有两面性,反射的优点,也同是就是它的缺点,所以,没有好与坏,只有最合适的场景,一阴一阳,才是天道平衡的条件。
二、Class
一切的反射操作,都源自于Class对象
通过Class对象可以获取类的相关信息。
每个类都有一个 Class 对象,它用来创建这个类的所有对象,反过来说,每个类的所有对象都会关联同一个 Class 对象
Class对象是类的代理对象,实例化对象时需要使用这个代码。
public class Student{
}
public class MainTest{
public static void main(String[] args) throws InterruptedException {
// 实例化对象,都会使用类背后的Class对象来实例化对象
Student zs = new Student();
Student ls = new Student();
Class clazz1 = zs.getClass();
Class clazz2 = ls.getClass();
System.out.println(clazz1 == clazz2); // true
}
}
Class只有一个私有的无参构造方法,也就是说Class的对象创建只有JVM可以完成。
最后,在反射操作中,我们就通过Class对象获取类的相关信息,进行可以在程序运行时可以动态的做相关的操作 - 那么,我们如何获取Class对象呢?
三、获得Class对象
3.1 方法一
可以动态的获取包名、类名
Class.forName("包名.类名")
public class MainTest01 {
public static void main(String[] args) throws ClassNotFoundException {
// 第一:获取类的Class 对象
Class<?> clazz = Class.forName("test00.word01.entity.Student");
// 第二:通过Class对象进行相关的操作 - 获取Student对象的所有方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
}
3.3 方法二
类名.class
String.class;
Student.class;
int.class;
void.class;
public class MainTest02 {
public static void main(String[] args) {
// 第一:获取类的 Class 对象
Class<?> clazz = Student.class;
// 第二:通过Class对象进行相关的操作
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
}
3.3 方法三
使用对象的getClass()方法和Class对象的getSuperclass()
String s = “aa”;
Class<?> clazz1 = s.getClass();
Class<?> clazz2 = clazz1.getSuperclass() ;
public static void test01() {
Student stu = new Student();
// 第一:获取类的 Class 对象
Class<?> clazz = stu.getClass();
// 第二:通过Class对象进行相关的操作
// 使用getClass()方法获取子类以及父类的所有方法(不包括私有方法)
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
public static void test02() {
Student stu = new Student();
// 第一:获取类的 Class 对象
Class<?> clazz = stu.getClass();
Class<?> superclass = clazz.getSuperclass();
// 第二:通过Class对象进行相关的操作
// 使用getSuperclass()方法只能获取父类的对象方法,不能获取子类的对象方法
Method[] methods = superclass.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
3.4 方法四 - 针对于包装类型
运用primitive wrapper classes的TYPE 语法
包装类.TYPE -> 获取包装类对应的基本类型的Class对象 如:Integer.TYPE :获取包装类Integer对应的基本类型的Class对象 -> int.class
// 注意:String.class;
Class c1 = Boolean.TYPE;
Class c2 = Byte.TYPE;
Class c3 = Character.TYPE;
Class c4 = Short.TYPE;
Class c5 = Integer.TYPE;
Class c6 = Long.TYPE;
Class c7 = Float.TYPE;
Class c8 = Double.TYPE;
Class c9 = Void.TYPE;
// 列如
Integer.TYPE == int.class; // true
Integer.TYPE == Integer.class; // false
public static void main(String[] args) throws Exception {
Student stu = new Student();
// 第一:获取类的 Class 对象
// Class<?> clazz = Class.forName("test00.entity.Student");
Class<?> clazz = stu.getClass();
// 第二通过Class对象中的方法来获取类的相关信息,并做相应的操作
// 1.获取类的构造方法
Constructor<?> constructor = clazz.getConstructor(String.class,Integer.TYPE,Double.TYPE);
// 2.通过构造方法实例化对象
Object obj = constructor.newInstance("张三", 18, 180);
// 向下转型
if (obj instanceof Student) {
stu = (Student) obj;
stu.show();
}
}
四、反射操作步骤(API操作)
1、操作步骤
第一:获取类的Class对象
第二:通过Class对象中的方法来获取类的相关信息,并做相应的操作
获取Class对象之后,就是通过Class对象来进行相关的API操作了,如实例化对象、Field、Method…
//第一:获取类表示的Class对象
Class<?> clazz = Class.forName("test01.entity.Student") ;
//第二:通过Class对象中的方法来获取类的相关信息,并做相应的操作
//注:只能使用默认构造方法来创建对象
//Object stu = clazz.newInstance() ;
// 1.获取类的构造方法对象
Constructor<?> constructor = clazz.getConstructor();
// 2.使用构造方法实例化对象
Object obj = constructop.newInstance();
// 3.向下转换
Student stu = (Student)clazz.newInstance() ;
stu.sayHello("小三");
2、实例化对象
注意:
- 获取类的无参构造方法:class对象.getConstructor();
- 获取类的有参构造方法:class对象.getConstructor(型参类型.class,型参类型.class,…);
1)缺省构造方法实例化对象
Class对象.newInstance()
// 第一:获取类表示的Class对象
Class<?> clazz = Class.forName("test01.entity.Student") ;
// 第二:通过Class对象的newInstance()方法来实例化对象
// 注:现在不建议使用默认构造方法来创建对象(已淘汰!)
// Object stu = clazz.newInstance() ;
// Student stu = (Student)clazz.newInstance() ;
// 1.获取类的构造方法对象
Constructor<?> constructor = clazz.getConstructor();
// 2.通过构造方法对象实例化对象
Object obj = constructor.newInstance();
// 3.向下转型
if(obj instanceof Student) {
Student stu = (Student) obj ;
stu.sayHello();
}
2)重载构造方法实例化对象
Constructor:这个类用于获取类中的构造方法的信息以及访问这些构造方法的能力
Constructor<?> ct = Class对象.getConstructor(型参类型.class,型参类型.class,…) ;
Student stu = (Student)ct.newInstance(实参值, 实参值,…) ;
//第一:获取类表示的Class对象
Class<?> clazz = Student.class;
//getConstructor(构造方法参数类型的class对象)
//第二:获取重载构造方法
Constructor<?> constructor = clazz.getConstructor(String.class,int.class,float.class) ;
//第三:创建重载构造方法的对象
//newInstance(实参列表)
Object obj = constructor.newInstance("张三",19,200) ;
Student stu = (Student)obj ;
//张三,19,200.0
System.out.println(stu);
注意:如果构造方法的参数为包装类型,则传递实参时必须为对应的包装类型
//第一:获取Student类的Class对象
Class<?> clazz = Student.class ;
//第二:获取Student中的构造方法
Constructor<?> cons = clazz.getConstructor(String.class,Float.TYPE) ;
//第三:调用构造方法并创建对象
//错误,类型不匹配
//Object obj = cons.newInstance("李四",20) ;
//解决方法一
//Object obj = cons.newInstance("李四",20.0F) ;
//解决方法二
Object obj = cons.newInstance("李四",new Float(20)) ;
if(obj instanceof Student) {
Student stu = (Student)obj ;
//李四,18,20.0
System.out.println(stu);
}
五、API
5.1 Constructor
某个类的构造方法对象
- newInstance()
5.2 Field
这个类用于获取类中的字段信息以及访问这些字段的能力。(字段的访问)
- 获取所有公开的字段(属性 - public)
Field[] fs = Class对象.getFields() ;
- 获取所有的字段(属性)
Field[] fs = Class对象.getDeclaredFields() ;
- 获取某个公开字段(属性)
Field field = clazz.getField(“字段名称”);
- 获取某个字段 (包括私有)
Field field = clazz.getDeclaredField(“字段名称”);
- 设置私有字段可访问
Field对象.setAccessible(true);
- 设置属性
field对象.set(对象,值)
- 获取属性
field对象.get(对象)
- 获取属性名称
field对象.getName()
5.2.1 属性的访问
de// 1)获取所有公共的字段
Field[] fs = Class对象.getFields() ;
// 2)获取所有的字段
Field[] fs = Class对象.getDeclaredFields() ;
public static void main(String[] args) {
// 第一:获取类的Class对象
Class<Student> clazz = Student.class;
// 第二:通过Class对象中的方法来获取类的相关信息,并做相应的操作
// 1.获取类的公开字段(属性)
// Field[] fields = clazz.getFields();
// 2.获取类的所有字段(属性)
Field[] fields = clazz.getDeclaredFields();
// 3.循环遍历数据字段的相关信息
System.out.println("字段的数量:" + fields.length);
for (Field field : fields) {
// 3.1、获取字段名称
System.out.println("字段名称:" + field.getName());
// 3.2、获取数据类型的Class对象
Class<?> typeClazz = field.getType();
// getName():如果是对象类型,则返回完整的包名.类型名称
// getSimpleName():不管是什么类型,返回的都是简单的类型名称(没有包名)
String typeName = typeClazz.getName();
String typeSimpleName = typeClazz.getSimpleName();
// 3.3、获取访问修饰符
int intModifier = field.getModifiers();
String strModifier = Modifier.toString(intModifier);
System.out.println("字段类型名称(包名.类名):" + typeName);
System.out.println("字段类型名称(简单名称):" + typeSimpleName);
System.out.println("字段访问修饰符(整型常量):" + intModifier);
System.out.println("字段访问修饰符(字符串):" + strModifier);
System.out.println();
}
}
5.2.2 属性的操作
// 第一:获取要操作的字段(属性)
Field field = clazz.getField(“字段名称”);
Field field = clazz.getDeclaredField(“字段名称”);
// 第二:设置属性值
field对象.set(对象,值)
注意:如果要访问私有的字段,必须要开启访问权限
Field对象.setAccessible(true);
// 第三:获取属性值
field对象.get(对象)
5.3 Modeifier
访问修饰符这个类用于获取类中的方法的信息以及访问这些方法的能力(修饰符的访问)。
这个类存放了所有修饰符的常量值,以及通过这些常量值判断是哪种修饰符。
- getModifiers():返回修改符的常量值(整数)
- Modifier.toString(修饰符常量值) :返回修饰符常量值对应的字符串表示
- isXxx(修饰符常量值):判断修饰符是否为Xxx
int modifier = Class对象.getModifiers() ;
int modifider = Field对象. getModifiers() ;
int modifider = Method对象. getModifiers() ;
int modifider = Constructor对象. getModifiers() ;
…
以上返回的是修饰符对应的整型常数,可以通过Modifier.toString(修饰符常量值)转为字符串表示。
Class<?> clazz = Student.class ;
Constructor<?> ct = clazz.getConstructor() ;
int mod = ct.getModifiers() ;
// 输出:1
System.out.println("修饰符(常量值):" + mod);
// 是否为public修饰符 - true
System.out.println("是否为public修饰符:" + Modifier.isPublic(mod));
// 输出:public
System.out.println("修饰符(字符串):" + Modifier.toString(mod));
// 获取类的修饰符
int intModifiers = Class对象.getModifiers();
String strModifiers = Class对象.toString(intModifiers);
判断是否为指定的修饰符
// 是否为public修饰符
boolean flag = Modfier.isPublic(intModifiers);
5.4 Method
这个类用于获取类中的方法的信息以及访问这些方法的能力(方法的访问)。
5.4.1 获取类中的方法有以下方法
- 获取类中所有的公开方法,返回Method数组
Method[] methods = Class对象.getMethods() ;
- 获取为中所有的方法(包括私有),返回Method数组
Method[] methods = Class对象.getDeclaredMethods() ;
- 返回某个指定的公共方法
Method method = Class对象.getMethod(“方法名”[,”参数列表的Class对象”,…]) ;
- 返回某个指定的方法(包括私有)
Method method = Class对象.getDeclaredMethod(“方法名”[,”参数列表的Class对象”,…]) ;
- 方法对象
Method对象.invoke(对象[,实参列表])
5.4.2 获取方法返回值类型的类类型
Class<?> typeClass = Method对象.getReturnType() ;
获取方法返回值类型名称
//如果是对象,则返回包类.类名,否则只返回名称
String name = typeClass.getName() ;
//不管是对象类型还是基本类型,都只返回名称
String simpleName = typeClass.getSimpleName() ;
5.3 获取方法参数类型
// 1、
Class<?>[] paramTypesClass = Method对象.getParameterTypes() ;
for (Class<?> cpt : paramTypesClass) {
System.out.println(param.getName());
[//System.out.println](//System.out.println)(param.getSimpleName());
}
// 2、设置私有方法可访问
Method对象.setAccessible(true) ;
// 3、调用方法
Method对象.invoke(“方法所在的对象”[,”实参列表”]) ;
1)遍历所有的方法
// 第一:获取Student类的Class对象
Class<?> clazz = Student.class ;
// 第二:获取Student类中的所有方法
Method[] methods = clazz.getMethods() ;
// 第三:打印输出所有方法的名称
for (Method m : methods) {
System.out.println("方法名:"+m.getName());
System.out.println("访问修饰符:"+m.getModifiers());
System.out.println("访问修饰符:"+Modifier.toString(m.getModifiers()));
//Class<?> typeClass = m.getReturnType() ;
//String typeName = typeClass.getName() ;
//String typeName = typeClass.getSimpleName() ;
String typeName = m.getReturnType().getSimpleName() ;
System.out.println("返回值类型:"+typeName);
//获取方法参数类型的Class对象
Class<?>[] clz = m.getParameterTypes() ;
System.out.println("参数个数:"+clz.length);
//遍历方法中的参数类型
for (Class<?> param : clz) {
//System.out.println(param.getName());
System.out.println(param.getSimpleName());
}
System.out.println();
}
2)获取某个方法并调用
// 第一:获取Student类的Class对象
Class<?> clazz = Student.class;
//第二:创建Student类的对象
Constructor<?> constructor = clazz.getConstructor(String.class,Float.class) ;
Object obj = constructor.newInstance("王八",200.0F) ;
// 第三:获取Student类中"公有的"sum方法
Method m = clazz.getMethod("sum", int.class,int.class) ;
// 第四:调用方法
Object oj = m.invoke(obj, 1,1) ;
//输出:2
System.out.println(oj.toString());
3)调用私有方法
// 第一:获取Student类的Class对象
Class<?> clazz = Student.class;
// 第二:创建Student类的对象
Constructor<?> constructor = clazz.getConstructor(String.class,Float.class) ;
Object obj = constructor.newInstance("王八",200.0F) ;
// 第三:获取Student类中"私有的"sleep方法
//Method m = clazz.getMethod("sleep") ; //获取失败
Method m = clazz.getDeclaredMethod("sleep") ;
// 第四:设置私有方法可访问,否则访问失败
m.setAccessible(true) ;
// 第五:调用方法
m.invoke(obj) ;
5.5 Package
// 包的操作
Package p = clazz.getPackage();
// 获取包的名称
String packageName = p.getName();
案例:
// 第一:获取类的Class对象
Class<?> clazz = Student.class ;
// 第二:通过Class对象中的方法来获取类的相关信息,并做相应的操作
// 1.获取类的包对象
Package p = clazz.getPackage();
// 2.获取包的名称
String packageName = p.getName();
//test01.entity
System.out.println("Student类所在包为:" + packageName);
5.6 数组
这个类提供了动态创建和访问数组的能力
5.6.1 创建数组
Object obj = Array.newInstance(“数组数据类型的Class对象”, “长度”) ;
// 向下转型
int[] arrInt = (int[])obj;
String[] arrString = (String[])obj;
...
5.6.2 初始化数组
// set方法是用object接收
Array.set(“数组对象” , ”下标索引” , “值”) ;
// setXxx方法是用相应的类型接收
Array.setXxx(“数组对象” , ”下标索引” , “值”) ;
5.6.3 获取数组中的元素
// get方法是用object接收
Array.get(“数组对象”, “下标索引”) ;
// getXxx方法是用相应的类型接收
Array.getXxx(“数组对象”, “下标索引”) ;
5.6.4 获取数组中的索引(下标)
Object obj = Array.newInstance(“数组数据类型的Class对象”, “长度”) ;
// 获取数组的长度
int len = Array.getLength(obj);
六、一维数组
6.1 数组的传统使用
// 第一:创建数组
// int[] arr = new int[3];
// 第二:初始化数组
// arr[0] = 100;
// arr[1] = 200;
// arr[2] = 300;
// 在创建数组的同时,初始化数组
int[] arr = {100,200,300};
// 第三:使用数组(循环)
System.out.println("第一个数组:" + arr[0]);
System.out.println("第二个数组:" + arr[1]);
System.out.println("第三个数组:" + arr[2]);
6.2 数组的反射操作
// 第一.:实例化数组对象 - 创建数组
Object arrObj = Array.newInstance(int.class, 3) ;
// 向下转型
// int[] arr = (int[])arrObj ;
// 第二:初始化数组
Array.set(arrObj , 0 , 100) ;
Array.set(arrObj , 1 , 200) ;
Array.setInt(arrObj , 2 , 300) ;
// 第三:获取数组中的元素值 - 使用数组
Object oneValue = Array.get(arrObj, 0) ;
Object twoValue = Array.get(arrObj, 1) ;
int threeValue = Array.getInt(arrObj, 2) ;
System.out.println("第一个数组:" + oneValue);
System.out.println("第二个数组:" + twoValue);
System.out.println("第三个数组:" + threeValue);
6.3 反射的多维数组操作
// Object objSan = Array.newInstance(“数组数据类型的Class对象”, [维数,...]);
// 二维数组
Object twoObj = Array.newInstance(int.class, 3, 4);
// 在本地存储开辟了6个存储空间(2 * 3)
int[][] obj = new int[2][3];
// 三维数组
Object threeObj = Array.newInstance(int.class, 3, 4,2);
// 在本地存储开辟了34个存储空间(3 * 4 * 2)
int[][][] obj = new int[2][3][4];
...
案例一:
// 第一:实例化多维数组(三维) - int[][][] obj = new int[3][4][7];
Object obj = Array.newInstance(int.class,3,4,7);
// 第二:初始化多维数组 - 分别获取各维数组的引用 - obj[1][2][5]
// 1.获取第一维数组的引用(把三维数组的引用转换为二维数组的引用) - obj[1][?]
Object oneArr = Array.get(obj,1);
// 2.获取第二维数组的引用(把二维数组的引用转换为一维数组的引用) - obj[1][2][?]
Object twoArr = Array.get(oneArr,2);
// 3.赋值 - 给三维数组的引用赋值
Array.set(twoArr,5,100);
// 4.取值
// Object data = Array.get(twoArr,5);
// 第三:使用多维数组
Object data = Array.get(twoArr,5);
// 输出:100
System.out.println(data);
// 获取多维数组的长度
System.out.println("第一维的长度:" + Array.getLength(obj));
System.out.println("第二维的长度:" + Array.getLength(oneArr));
System.out.println("第三维的长度:" + Array.getLength(twoArr));
七、泛型
泛型只能在编译时有效,如果跳过编译,在运行时可以破坏泛型的约束(反射)。
7.1 泛型在编译时的约束
-- 泛型的基本操作
// 实例化一个List集合,并指定泛型
// 注意:泛型只能在编译时有效
List<String> list = new ArrayList<>() ;
// 添加元素
list.add("你好");
list.add("我好");
list.add("大家好,才是真的好");
// 以下代码报错,因为已经通过泛型指定集合只能存储字符串类型数据
// 字符串以外的数据是不能存储的 - 此约束只能作用在编译期
// 如果我们跳过编译期,能否存储其它类型的数据呢 - 可以 - 反射 - 在运行时做一些处理
// list.add(100) ;
7.2 破坏泛型的约束
// 实例化一个List集合,并指定泛型
List<String> list = new ArrayList<String>();
// 通过反射来添加元素
// 第一:获取List对象的Class对象
Class<?> listClass = list.getClass();
// 第二:通过Class对象中的方法来获取类的相关信息,并做相应的操作
// 1.获取要操作的方法对象 - list.add("");
Method addMethod = listClass.getMethod("add", Object.class);
// 2.调用方法
addMethod.invoke(list, "AAA");
addMethod.invoke(list, 100);
addMethod.invoke(list, 3.14);
addMethod.invoke(list, false);
// 3.获取集合中的数据 - 注意:不能使用循环 - 会报 ClassCastException 错误
// 注意:循环过程中,需要把各种类型的数据转换为字符串 - String.valueOf()
for(Object obj: list){
String s = String.valueOf(obj)
System.out.println(s);
}
// String s1 = list.get(0);
// String s2 = list.get(1);
// String s2 = String.valueOf(list.get(1));
// String s3 = String.valueOf(list.get(2));
// String s4 = String.valueOf(list.get(3));
// System.out.println("第一个元素:" + s1);
// System.out.println("第二个元素:" + s2);
// System.out.println("第三个元素:" + s3);
// System.out.println("第四个元素:" + s4);
7.3 如何在Servlet中使用反射机制:
第一:定义一个Servlet的父类 - 通过反射机制,在程序运行时调用action参数指定方法
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 第一:获取类的Class对象 - clazz对象运行时动态决定
Class clazz = this.getClass();
// 获取action参数值
String action = req.getParameter("action");
// 第二:通过Class对象中的方法来获取类的相关信息,并做相应的操作
try {
// 1.获取要调用的方法对象 - 要指定具体的接口对象(而不是随意的req.getClass、resp.getClass)
// 注意:HttpServletRequest 与 HttpServletResponse 是个接口对象,而不是个类!
Method method = clazz.getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
// 2.开启访问权限
method.setAccessible(true);
// 3.调用方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
do405(req,resp);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
protected void do405(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("request error...");
}
}
第二:定义一个 Servlet 可以处理 Student 相关的请求
@WebServlet("/student.do")
public class StudentServlet extends BaseServlet{
/**
* 处理 添加学生
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("add...");
}
/**
* 处理 删除学生
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void del(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("del...");
}
/**
* 处理 修改学生
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("update...");
}
/**
* 处理 查询学生
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void query(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("query...");
}
}
第三:在HTML中使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>使用反射操作,实现对Servlet程序进行封装 -- 减少Servlet编写</h1>
<h3>实现一个Servlet可以处理多个Http请求</h3>
<hr/>
<h1>之前的操作 - 每一个请求对应一个Servlet程序(多、不便于维护)</h1>
<h3><a href="add.do">添加学生</a></h3>
<h3><a href="del.do">删除学生</a></h3>
<h3><a href="update.do">修改学生</a></h3>
<h3><a href="query.do">查询学生</a></h3>
<h1>现在的操作 - 使用反射封装Servlet,实现一个Servlet可以处理多个请求</h1>
<h3><a href="student.do?action=add">添加学生</a></h3>
<h3><a href="student.do?action=del">删除学生</a></h3>
<h3><a href="student.do?action=update">修改学生</a></h3>
<h3><a href="student.do?action=query">查询学生</a></h3>
</body>
</html>