话说:
在JavaWeb-Servlet-News(CURD)这篇博客里面,我们用Servlet改造了新闻(News),在JavaWeb-Servlet-Properties、JavaWeb-Servlet-Reflect两篇里面,分别做了优化。今天继续走在优化的道路上吧!
目标
1、优化多个Servlet的局面
1)根据参数选择不同的Servlet
2)利用反射机制,动态选择调用方法
2、错误页面处理(404、500、400)
1、优化多个Servlet的局面
1)根据参数选择不同的Servlet
细心的读者会发现,之前笔者用Servlet来写CURD的时候,每实现一个功能,就建立了一个对应的Servlet,如图:
这样很麻烦,一个项目里面,有几十个功能,那都这么写……..会哭的。所以嘛,我们希望可以再一个Servlet里面写多个Servlet,或者实现类似的功能。我们创建一个NewsServlet,希望用它来达到一劳永逸的效果。
上代码:
在Servlet包下面新建一个NewsServlet类:
package com.hmc.jdbc.news.servlet;
import com.hmc.jdbc.news.dao.NewsDao;
import com.hmc.jdbc.news.model.News;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* User:Meice
* 2017/10/5
*/
public class NewsServlet extends HttpServlet {
NewsDao nd = new NewsDao();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
//根据传递参数决定方法跳转 op代表你访问页面传递的参数operation之含义
String op = req.getParameter("op");
if(op !=null && !"".equals(op)) {
if("list".equals(op)) {
System.out.println("调用newsShow()方法");
}else if("add".equals(op)) {
System.out.println("调用newsAdd()方法");
}else if("update".equals(op)) {
System.out.println("调用newsUpdate()方法");
}else if("del".equals(op)) {
System.out.println("调用newsDel()方法");
}else {
System.out.println("参数传递有误");
}
}else{
System.out.println("参数缺失");
}
}
}
配置web.xml如下:
(为了保持思维连贯性,把整个web.xml配置都贴了出来,请看最后那一组配置)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--news Add Servlet-->
<servlet>
<servlet-name>newsAdd</servlet-name>
<servlet-class>com.hmc.jdbc.news.servlet.NewsAddServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>newsAdd</servlet-name>
<url-pattern>/newsAdd.do</url-pattern>
</servlet-mapping>
<!--news Show Servlet-->
<servlet>
<servlet-name>newsShow</servlet-name>
<servlet-class>com.hmc.jdbc.news.servlet.NewsShowServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>newsShow</servlet-name>
<url-pattern>/newsShow.do</url-pattern>
</servlet-mapping>
<!-- news Update Servlet-->
<servlet>
<servlet-name>newsUpdate</servlet-name>
<servlet-class>com.hmc.jdbc.news.servlet.NewsUpdateServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>newsUpdate</servlet-name>
<url-pattern>/newsUpdate.do</url-pattern>
</servlet-mapping>
<!--news Update Do Servlet-->
<servlet>
<servlet-name>newsUpdateDo</servlet-name>
<servlet-class>com.hmc.jdbc.news.servlet.NewsUpdateDoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>newsUpdateDo</servlet-name>
<url-pattern>/newsUpdateDo.do</url-pattern>
</servlet-mapping>
<!--news Del Servlet-->
<servlet>
<servlet-name>newsDel</servlet-name>
<servlet-class>com.hmc.jdbc.news.servlet.NewsDelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>newsDel</servlet-name>
<url-pattern>/newsDel.do</url-pattern>
</servlet-mapping>
<!--news Servlet 这个Servlet可以处理所有操作;只要你给我一个参数-->
<servlet>
<servlet-name>news</servlet-name>
<servlet-class>com.hmc.jdbc.news.servlet.NewsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>news</servlet-name>
<url-pattern>/news.do</url-pattern>
</servlet-mapping>
</web-app>
这里为了简单阐述清楚访问的情况,我们先简单的输出结果,看是否如此:
假如你在页面访问:
localhost:8080/JavaWeb_Servlet/news.do?op=list
localhost:8080/JavaWeb_Servlet/news.do?op=add
localhost:8080/JavaWeb_Servlet/news.do?op=update
localhost:8080/JavaWeb_Servlet/news.do?op=del
,控制台就输出调用对应的方法。因此,我们只需要把之前其他类似NewsAddServlet、NewsDelServlet…中的代码拷贝到对应位置即可,对跳转做酌情修改。
为了让界面更加整洁,我就补充两个功能的代码:list和del;补充后代码如下:
package com.hmc.jdbc.news.servlet;
import com.hmc.jdbc.news.dao.NewsDao;
import com.hmc.jdbc.news.model.News;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* User:Meice
* 2017/10/5
*/
public class NewsServlet extends HttpServlet {
NewsDao nd = new NewsDao();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
//根据传递参数决定方法跳转 op代表你访问页面传递的参数operation之含义
String op = req.getParameter("op");
if(op !=null && !"".equals(op)) {
if("list".equals(op)) {
// System.out.println("调用newsShow()方法");
//1 处理编码(抽离出来了,在doPost()一开始统一处理)
//2 处理业务逻辑,调用方法
List<News> list = nd.list();
//3 存储到req中
req.setAttribute("list",list);
req.getRequestDispatcher("newsShow.jsp").forward(req,resp);
}else if("add".equals(op)) {
// System.out.println("调用newsAdd()方法");
}else if("update".equals(op)) {
System.out.println("调用newsUpdate()方法");
}else if("del".equals(op)) {
// System.out.println("调用newsDel()方法");
//1 接受参数
int id = 0;
String strId = req.getParameter("id");
if(strId != null && !"".equals(strId)) {
id = Integer.valueOf(strId);
}
//2 业务逻辑处理(按照id删除对应新闻)
int result = nd.newsDel2(id);
//3 页面跳转
if(result >0 ) {
req.getRequestDispatcher("newsShow.do").forward(req,resp);
}else {
req.getRequestDispatcher("newsShow.do").forward(req,resp);
}
}else {
System.out.println("参数传递有误");
}
}else{
System.out.println("参数缺失");
}
}
}
总结:
以上代码,把设置编码抽离了出来,统一在doPost()方法里面设置,只用设置一次;NewsDao实例化也是在类一开始就实例化了,因为后面的方法基本都要用到,未避免多次实例化,占用内存和资源,只实例化一次。
然鹅:如果功能一多起来,看起来还是混乱,不便于整理。所以,我们把相关功能方法封装起来,在判断的时候直接调用方法。改进如下:
package com.hmc.jdbc.news.servlet;
import com.hmc.jdbc.news.dao.NewsDao;
import com.hmc.jdbc.news.model.News;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* User:Meice
* 2017/10/5
*/
public class NewsServlet extends HttpServlet {
NewsDao nd = new NewsDao();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
//根据传递参数决定方法跳转 op代表你访问页面传递的参数operation之含义
String op = req.getParameter("op");
if(op !=null && !"".equals(op)) {
if("list".equals(op)) {
// System.out.println("调用newsShow()方法");
list(req,resp);
}else if("add".equals(op)) {
// System.out.println("调用newsAdd()方法");
}else if("update".equals(op)) {
System.out.println("调用newsUpdate()方法");
}else if("del".equals(op)) {
// System.out.println("调用newsDel()方法");
Del(req,resp);
}else {
System.out.println("参数传递有误");
}
}else{
System.out.println("参数缺失");
}
}
//未避免代码混乱,把显示新闻列表封装成方法
private void list(HttpServletRequest req,HttpServletResponse resp) {
List<News> list = nd.list();
//3 存储到req中
req.setAttribute("list",list);
try {
req.getRequestDispatcher("newsShow.jsp").forward(req,resp);
} catch (ServletException e) {
} catch (IOException e) {
}
}
//同理,把删除封装成方法
private void Del(HttpServletRequest req,HttpServletResponse resp) {
//1 接受参数
int id = 0;
String strId = req.getParameter("id");
if(strId != null && !"".equals(strId)) {
id = Integer.valueOf(strId);
}
//2 业务逻辑处理(按照id删除对应新闻)
int result = nd.newsDel2(id);
//3 页面跳转
try {
if(result >0 ) {
req.getRequestDispatcher("newsShow.do").forward(req,resp);
}else {
req.getRequestDispatcher("newsShow.do").forward(req,resp);
}
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
然鹅,这表面看起来简洁了些,本质还不是需要自己去调用,下面我们利用反射机制,利用Method类来实现动态选择方法(自动选择)
2)利用反射机制,动态选择调用方法
要实现的目标就是避免用if () else{}来判断调用哪个方法。你参数传递的是什么方法名(?op= ),我就调用与参数op同名的方法。
package com.hmc.jdbc.news.servlet;
import com.hmc.jdbc.news.dao.NewsDao;
import com.hmc.jdbc.news.model.News;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
/**
* User:Meice
* 2017/10/5
*/
public class NewsServlet extends HttpServlet {
NewsDao nd = new NewsDao();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
//根据传递参数决定方法跳转 op代表你访问页面传递的参数operation之含义
String op = req.getParameter("op");
if(op !=null && !"".equals(op)) {
try {
//获取Method对象;这里和我们前面写过的this.getClass().getDeclaredField类似
Method method = this.getClass().getDeclaredMethod(op,HttpServletRequest.class,HttpServletResponse.class);
/**
* 调用invoke()方法;属于java.lang.reflect下面的类
* 自动调用指定的方法 参数1:方法所在的类 参数2:调用方法的参数
*/
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}else{
System.out.println("参数缺失");
}
}
//未避免代码混乱,把显示新闻列表封装成方法
private void list(HttpServletRequest req,HttpServletResponse resp) {
List<News> list = nd.list();
//3 存储到req中
req.setAttribute("list",list);
try {
req.getRequestDispatcher("newsShow.jsp").forward(req,resp);
} catch (ServletException e) {
} catch (IOException e) {
}
}
//同理,把删除封装成方法
private void Del(HttpServletRequest req,HttpServletResponse resp) {
//1 接受参数
int id = 0;
String strId = req.getParameter("id");
if(strId != null && !"".equals(strId)) {
id = Integer.valueOf(strId);
}
//2 业务逻辑处理(按照id删除对应新闻)
int result = nd.newsDel2(id);
//3 页面跳转
try {
if(result >0 ) {
req.getRequestDispatcher("newsShow.do").forward(req,resp);
}else {
req.getRequestDispatcher("newsShow.do").forward(req,resp);
}
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:
1、获取Method类的对象
用Class的方法来获取Method对象,前提是给我一个和想要调用方法名相同的String,和参数的类。
后面的各种框架,就很简单了,调用什么方法加注解,而且对post 、get方法区分很明确。
2、调用反射类Method的invoke()方法
调用方法也很简单,给方法所在的类,和方法包含的参数即可。之所以知道调用方法名和op相同的方法,是因为你创建了Method这个对象,这个对象就根据你传过来的参数op底层映射成了对应名称相等的方法。
这样就简单了,想用什么方法NewsUpdate、NewsAdd等方法直接接着写既可以啦。
这里还存在一个问题,假如我有不同的业务类,需要调用,那么遇到的问题和我们优化查最后一步相似,我们也需要写重复类似调用Method类的方法吗?
所以,封装起来。新建一个BaseServlet类,把共性代码封装,然后这个NewsServlet的实例extends一下就好。这里我们不用重写doGet()
doPost(),我们重写Service,不论什么请求,都可以处理。
BaseServlet
package com.hmc.jdbc.news.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* User:Meice
* 2017/10/5
*/
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
//根据传递参数决定方法跳转 op代表你访问页面传递的参数operation之含义
String op = req.getParameter("op");
if(op !=null && !"".equals(op)) {
try {
//获取Method对象;这里和我们前面写过的this.getClass().getDeclaredField类似
Method method = this.getClass().getDeclaredMethod(op,HttpServletRequest.class,HttpServletResponse.class);
/**
* 调用invoke()方法;属于java.lang.reflect下面的类
* 自动调用指定的方法 参数1:方法所在的类 参数2:调用方法的参数
*/
//如果不取消访问权限检查,会报IllegalAccessException异常
method.setAccessible(true);
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}else{
System.out.println("参数缺失");
}
}
}
NewsServlet改造后变成这样:
package com.hmc.jdbc.news.servlet;
import com.hmc.jdbc.news.dao.NewsDao;
import com.hmc.jdbc.news.model.News;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* User:Meice
* 2017/10/5
*/
public class NewsServlet extends BaseServlet {
NewsDao nd = new NewsDao();
//未避免代码混乱,把显示新闻列表封装成方法
private void list (HttpServletRequest req, HttpServletResponse resp){
List<News> list = nd.list();
//3 存储到req中
req.setAttribute("list", list);
try {
req.getRequestDispatcher("newsShow.jsp").forward(req, resp);
} catch (ServletException e) {
} catch (IOException e) {
}
}
//同理,把删除封装成方法
private void Del(HttpServletRequest req,HttpServletResponse resp) {
//1 接受参数
int id = 0;
String strId = req.getParameter("id");
if (strId != null && !"".equals(strId)) {
id = Integer.valueOf(strId);
}
//2 业务逻辑处理(按照id删除对应新闻)
int result = nd.newsDel2(id);
//3 页面跳转
try {
if (result > 0) {
req.getRequestDispatcher("newsShow.do").forward(req, resp);
} else {
req.getRequestDispatcher("newsShow.do").forward(req, resp);
}
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里只写了CURD的查,只要查没问题,其他直接把方法搬过来即可。
需要注意,如果没有设置method.setAccessible(true)的话,因为我们的NewsServlet中的方法是Private的,所以会报访问权限检查异常。
java.lang.IllegalAccessException: Class com.hmc.jdbc.news.servlet.BaseServlet can not access a member of class com.hmc.jdbc.news.servlet.NewsServlet with modifiers "private"
这个异常很熟悉了,之前在优化BaseDao实例化Field对象的时候,遇到过。因为是反射类,所以他们一般都容易报这样的异常。可以把Private变为public?这样确实就不会访问检查了。不过,这样就得不偿失了,我们目的就是要Private,违背了封装的本意,因小失大。
至此,我们的Servlet优化告一段落。优化的过程,如同修行啊,嘻嘻;
温故而知新。
然鹅,今天的内容还没有结束,下午出去吃了一顿好的,所以在来点产出把!也请感兴趣滴读者有耐心看下去。以下内容比较好玩。
2、错误页面处理(404、500、400)
一旦访问不小心,访问到了我们没有写的方法,比如
localhost:8080/JavaWeb_Servlet?op=list6666
我们就没有list6666()这样的方法,怎么调用?所以界面什么也不显示,多尴尬!同时,后台报错:
java.lang.NoSuchMethodException: com.hmc.jdbc.news.servlet.NewsServlet.list34(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
找不到方法丫!所以,为了避免这样,我们起码给客户一个反馈,出现一个页面,来提示一下。以挽回我们编程人员的一点点面子。奥,报错啊。我知道的,你看看那个错误页面,就是我写的,一切都在掌握之中,哈哈。
不让程序抛出异常,而是访问我们事先准备好的错误页面,有2种方法。
法1:在try() {}代码块中写错误页面跳转代码
法2:在web.xml中做全局错误页面配置
法1:在try() {}代码块中写跳转代码
1)先准备一个错误显示页面,以及显示图片。
页面error.jsp
<%--
User: Meice
Date: 2017/10/5
Time: 21:33
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>找小美处理吧</title>
</head>
<body>
<h2>嘻嘻,页面不见了,请不要慌张,不要失望</h2>
<img src="images/error.jpg" alt="请找程序猿小美处理奥">
</body>
</html>
修改后,整体布局是这样的:
修改抛出异常代码:
请只用关注: resp.sendRedirect(“error.jsp”);这个位置就好。可惜的是MarkDown不能标记颜色。
package com.hmc.jdbc.news.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* User:Meice
* 2017/10/5
*/
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
//根据传递参数决定方法跳转 op代表你访问页面传递的参数operation之含义
String op = req.getParameter("op");
if(op !=null && !"".equals(op)) {
try {
//获取Method对象;这里和我们前面写过的this.getClass().getDeclaredField类似
Method method = this.getClass().getDeclaredMethod(op,HttpServletRequest.class,HttpServletResponse.class);
/**
* 调用invoke()方法;属于java.lang.reflect下面的类
* 自动调用指定的方法 参数1:方法所在的类 参数2:调用方法的参数
*/
//如果不取消访问权限检查,会报IllegalAccessException异常
method.setAccessible(true);
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
//这里,我们来掌控,不要让它抛出异常即可。因为不需要携带什么参数,有请重定向上场!
//e.printStackTrace();
resp.sendRedirect("error.jsp");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}else{
System.out.println("参数缺失");
}
}
}
有个小细节需要注意下:
resp.sendRedirect(“error.jsp”);如果加上/的话,直接从根目录开始访问。比如我们访问路径:
http://localhost:8080/JavaWeb_Servlet/news.do?op=list6666
直接变成这样:
http://localhost:8080/error.jsp
所以,应该这么访问,就会默认带上上下文路径(项目路径):
resp.sendRedirect(“error.jsp”);
提示页面如下:
法2:在web.xml中做全局错误页面配置
除了在代码里面写异常处理跳转,web.xml还提供了直接进行页面跳转的方式:
配置web.xml错误页面:
<!--错误页面配置-->
<error-page>
<!--表示页面错误类型404 400 500之类的-->
<error-code>404</error-code>
<!--这里配置错误页面位置-->
<location>/error.jsp</location>
<!--这个配置是配置错误类型类似:NullPointException -->
<!-- <exception-type></exception-type>-->
</error-page>
注意,这里节点也是有顺序的。同时,要确保你页面错误是404,(页面找不到)这样的错误,才会出现以上界面奥。
好了,今天更新到这里,我也累了。
冰雪聪明的你,喜欢那种方式呢?
本文通过使用Servlet优化新闻管理功能,介绍了如何在一个Servlet中处理多种操作,利用反射机制动态选择调用方法,并实现了错误页面的处理。
1195

被折叠的 条评论
为什么被折叠?



