JavaWeb阶段性项目1:系统的servlet优化2
前置知识
前置准备
知识准备
已掌握JavaSE/MySQL/JDBC+HTML/CSS/JavaScript基础
并已完成了Javaweb前置知识的学习
04-JavaScript基础应用-鼠标悬浮/离开表格格式变化
05-JavaWeb-Tomcat8安装、Servlet初识
06-JavaWeb-Servlet方法/生命周期、HTTP/会话session
09-JavaWeb-阶段性项目1:最简单的后台库存管理系统
10-JavaWeb阶段性项目1:系统的servlet优化1
资源准备
教学资源
https://pan.baidu.com/s/1TS7QJ_a2vHHmXkggAs8RMQ
提取码:yyds
servlet优化的过程2
当Fruitservlet中的方法过多,会导致switch case判断语句过长
使用反射优化switch-case
优化如下:
@WebServlet("/fruit.do")
public class FruitServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String operate = req.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index";
}
//获取当前类中所有的方法
Method[] methods = this.getClass().getDeclaredMethods();
for (Method m:
methods) {
//获取方法名称
String methodName = m.getName();
if(operate.equals(methodName)){
//表示找到和operate同名的方法,那么通过反射技术调用它
m.invoke(this,req,resp);
}
}
这里顺便复习一下Java的反射机制,是其成为动态语言的关键。
这里的FruitServlet类加载完后,我们可以对其进行以下操作:
Java反射机制提供的功能
-
在运行时判断任意一个对象所属的类
-
在运行时构造任意一个类的对象
-
在运行时判断任意一个类所具有的成员变量和方法
-
在运行时获取泛型信息
-
在运行时调用任意一个对象的成员变量和方法
-
在运行时处理注解
-
生成动态代理
这里我们要调用它的方法
用到了getDeclaredMethods()方法来获取了FruitServlet类中的所有方法
获取之后使用invoke方法来调用
传入的参数是this:当前类的对象,FruitServlet中的每一个方法刚好对应相同的参数(request,respond)
注意:这里获取方法是getDeclaredMethods(),才能获取到private,因为在是在本类中调用
引入DispatcherServlet
所有的请求交给DispatcherServlet,根据请求的不同定位不同的servlet,再定位到不同的方法。
FruitServlet改名为FruitController(控制器)
增加DispatcherServlet类
@WebServlet("*.do")
①拦截所有以.do结尾的请求
②假设URL是http://localhost:8080/pro15/hello.do
通过req.getServletPath();获取请求中的“/hello.do”
③字符串截取到“hello”,发送给HelloController(类似之前的add.html页面发送add给FruitServlet)
String servletPath = req.getServletPath();
servletPath = servletPath.substring(1);
int lastDotIndex = servletPath.lastIndexOf(".do");
servletPath = servletPath.substring(0,lastDotIndex);
④hello和helloController对应上
将servlet文件夹改名为controllers
去掉@WebServlet("/fruit.do"),让FruitController变为一个普通的类
创建hello和helloController对应说明文件,在src下创建applicationContext.xml
这里介绍到xml的概念
xml介绍
概念
HTML : 超文本标记语言 XML : 可扩展的标记语言 HTML是XML的一个子集
体会xml的可扩展
XML包含三个部分:
-
XML声明 , 必须要有,而且声明这一行代码必须在XML文件的第一行
-
DTD 文档类型定义(可有可无)
-
XML正文
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<!-- 这个bean标签的作用是将来servletpath中涉及的名字对应的fruit,那么就要FruitController这个类来处理-->
<bean id="fruit" class="com.fancy.fruit.controllers.FruitController"></bean>>
</beans>
xml文件配置好后,在DispatherServlet类的构造器中添加代码,让该类在实例化阶段的构造器中解析刚才配置的xml配置文件。
@WebServlet("*.do")
public class DispatherServlet extends ViewBaseServlet{
//servlet有加载-实例化-服务-销毁的生命周期,所以先在实例化阶段的构造器中解析xml配置文件
public DispatherServlet(){
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applictationContext.xml");
}
这里复习一下getClassLoader().getResourceAsStream("applictationContext.xml")这两个方法的相关知识。
复习类的加载与ClassLoader
类的加载过程
加载
-
.class文件加载到内存中
-
将静态数据转换成方法区的运行时数据
-
生成java.lang.Class对象(作为方法区中类数据的访问入口,即引用地址)
链接
验证-准备-解析
初始化
-
执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
-
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类 的初始化。
-
虚拟机会保证一个类的<clinit>(()方法在多线程环境中被正确加锁和同步。
什么时候发生类初始化
ClassLoader
类加载器作用是用来把类(class)装载进内存的。
getResourceAsStream方法
getResourceAsStream(String str):获取类路 径下的指定文件的输入流
所以这里我们获取到了applictationContext.xml的输入流
接下来创建Document对象,获取输入流中的bean标签。
public class DispatherServlet extends ViewBaseServlet{
//servlet有加载-实例化-服务-销毁的生命周期,所以先在实例化阶段的构造器中解析xml配置文件
public DispatherServlet(){
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applictationContext.xml");
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//创建document对象
Document document = documentBuilder.parse(inputStream);
NodeList beanNodeList = document.getElementsByTagName("bean");
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
通过IO流读取文件内容,再通过API获取文本中你设置好的节点的名字和属性
获取全类名后,要获取它的实例对象,这样就获得了beanId、beanObj。
最后将所有的bean和其对应的类实例对象绑定在一起,封装成map(差不多spring里的ioc)
public class DispatherServlet extends ViewBaseServlet{
private Map<String,Object> beanMap = new HashMap<>();
//servlet有加载-实例化-服务-销毁的生命周期,所以先在实例化阶段的构造器中解析xml配置文件
public DispatherServlet(){
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applictationContext.xml");
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//创建document对象
Document document = documentBuilder.parse(inputStream);
//获取所有的bean节点
NodeList beanNodeList = document.getElementsByTagName("bean");
for (int i = 0; i < beanNodeList.getLength(); i++) {
Node beanNode = beanNodeList.item(i);
//如果是一个元素节点,就强转为元素节点
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
Element beanElement = (Element) beanNode;
//获取了bean中的id属性、class属性
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
//获取全类名后,要获取它的实例对象
Object beanObj = Class.forName(className).newInstance();
//将beanId、beanObj放入Map中
beanMap.put(beanId,beanObj);
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
⑤得到了XXcontroller对象,成功调用了XX方法
回到下方的service方法,fruit现在是controller,传给前端控制器fruit前端控制器就会去实例化fruitcontroller。就像传hello会调用hellocontroller。(servletPath是“hello”)
接下来我们要调用该对象里的方法来进行我们请求的操作add、update、delete...
剪切service方法体,注意这里是剪切的pro14的
DispatcherServlet类
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String servletPath = req.getServletPath();
servletPath = servletPath.substring(1);
int lastDotIndex = servletPath.lastIndexOf(".do");
servletPath = servletPath.substring(0,lastDotIndex);
Object controllerBeanObj = beanMap.get(servletPath);
String operate = req.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index";
}
//获取当前类中所有的方法
Method[] methods = this.getClass().getDeclaredMethods();
for (Method m:
methods) {
//获取方法名称
String methodName = m.getName();
if(operate.equals(methodName)){
//表示找到和operate同名的方法,那么通过反射技术调用它
try {
m.invoke(this,req,resp);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
throw new RuntimeException("operate值非法!");
}
改方法体(通过反射获得所有该类的方法然后循环匹配调用该方法)
不用循环,改动
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String servletPath = req.getServletPath();
servletPath = servletPath.substring(1);
int lastDotIndex = servletPath.lastIndexOf(".do");
servletPath = servletPath.substring(0,lastDotIndex);
Object controllerBeanObj = beanMap.get(servletPath);
String operate = req.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index";
}
try {
Method method = controllerBeanObj.getClass().getDeclaredMethod(operate,HttpServletRequest.class,HttpServletResponse.class);
if (method != null){
method.invoke(this,req,resp);
}else {
throw new RuntimeException("operate值非法!");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
总结
①拦截所有以.do结尾的请求
②假设URL是http://localhost:8080/pro15/hello.do
③字符串截取到“hello”,发送给HelloController(类似之前的add.html页面发送add给FruitServlet)
④hello和helloController对应上(fruit和fruitController对应上)
⑤hello和helloController就对应上了,得到了XXcontroller对象,成功调用了XX方法
以上,通过读取xml配置文件,获取对应的beanId和className,再用反射加载className创建类的实例,放到map中,通过前端请求路径,获得对应的key,在DispatherServlet组件中通关反射获取对应XXController类,在对应类中通过反射获取对应类的方法,结合下图理解。
改动一下DispatherServlet集成的父类,改为HttpServlet