将WebServlet的访问路径不要写死,写成通配符的形式
1. 反射笔记(后续代码会用到该机制)
1.1 基础概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
言简意赅的说法就是:反射就是将Java类中各个成分映射成为一个个的Java对象
- 成员变量
- 方法、构造方法
- 包等等
但在此之前,先要把class类以及类的加载机制,在此基础上再探讨如何通过反射获取class类以及类的成员变量等等
1.1.1 Class类
Class类是一个实实在在的类,存在于java.lang包中;
Class类的实例标识java应用运行时的类或接口;
数组同样也被映射为class对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象;
手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件);
每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象;
Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载;
Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要
1.1.2 类加载
1.1.3 反射的使用
我们如何通过反射获取Class类对象以及类中的成员变量、方法、构造方法?
在类加载的时候,jvm会创建一个class对象;
在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持;
常用的类主要有Constructor类:表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象;
Field类:表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private);
Method类:表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)
1.1.4 反射机制的执行流程
1.1.5 反射小结
反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;
每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;
反射也是考虑了线程安全的,放心使用;
反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;
反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;
当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
调度反射方法,最终是由jvm执行invoke0()执行;
综述
反射是指程序在运行时可以访问、检测和修改其自身状态或行为的能力。在Java中,反射机制主要由java.lang.Class、java.lang.reflect.Method、java.lang.reflect.Field等类和接口组成,可以通过它们获取类信息、对象信息和方法信息等。
反射的原理是通过Java反射API,通过Java类加载器加载Java类文件到内存中,并且分析字节码文件,把类的各种成分(如类名、方法、属性等)映射成相应的Java类,去创建对应的类对象、方法对象、属性对象等。这些对象都存放在Java虚拟机的堆内存中。
具体来说,当需要对一个类进行反射时,JVM会首先利用类加载器读取类文件字节码,并转换成JVM能够理解的数据结构,然后创建一个Class对象,用来代表这个类。通过Class对象可以获取这个类的各种信息,如类名、方法、属性等,同时也可以创建该类的对象。
2. Servlet优化
问题一:之前在servlet中写WebServlet资源路径固定,如何解决?
路径不要写成固定,写成通配符形式
如:/brand/*
问题二:获取最后一段路径,也就是方法名称,获取了方法名称怎样执行方法?
通过反射机制,获取BrandServlet
的字节码文件,获取其方法的method对象,method.invoke()
就可以调用方法了
问题三:
BrandServlet extends HttpServlet
,而HttpServlet
在对应的service方法中通过request的请求方式做了方法分发,例如post请求就是采用doPost()
,那么在之前的servlet中我们只能执行doGet()
和doPost()
方法,但现在不写这两个方法,而是写自定义方法,这时就不能继承HTTPServlet
了,方法分发的功能也不采用了
而是采用自定义的Servlet,使用请求路径进行方法分发,替换HttpServlet的根据请求方式进行方法分发
针对问题三
不再采用请求方式进行方法分发,而是采用请求路径进行方法分发,所以这里重写service方法
下面代码思想非常关键
/**
* 替换HttpServlet,根据请求的最后一段路径来进行方法分发
*/
public class BaseServlet extends HttpServlet {
//根据请求的最后一段路径来进行方法分发
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 获取请求路径
String uri = req.getRequestURI(); // /brand-case/brand/selectAll
//2. 获取最后一段路径,方法名
int index = uri.lastIndexOf('/');
String methodName = uri.substring(index + 1); // /selectAll? selectAll?
//3. 执行方法
// 谁调用我(this),我代表谁
// BrandServlet会被Tomcat服务器调用,而它继承了BaseServlet
// 其中BaseServlet的service方法也是由Tomcat服务器调用,由BrandServlet调用
// 所以this指代的是BrandServlet
//System.out.println(this);
//3.1 获取BrandServlet /UserServlet 字节码对象 Class
Class<? extends BaseServlet> cls = this.getClass();
//3.2 获取方法 Method对象
try {
// 获取方法名,参数
Method method = cls.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
//3.3 执行方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
最后
修改前端页面的URL路径即可