JavaWeb-Servlet编程详解

一、Servlet介绍

什么是Servlet

  • Servlet是Java提供的一门动态Web资源开发技术
  • Servlet是JavaEE规范之一,其实就是一个接口,我们需要定义Servlet类实现Servlet接口,并且Web服务器运行Servlet。

二、Servlet原生方式开发步骤

1、创建web项目,导入Servlet依赖坐标

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.1.0</version>
	<scope>provided</scope>
</dependency>

2、创建:定义一个类实现Servlet接口,并重写接口中所有方法。

public class ServletDemo implements Servlet {

    /**
     *   初始化方法
     *   1.调用时机:默认情况下,Servlet被第一次访问时调用(更改loadOnStartup)     · 
     *   2.调用次数:1次 */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
    }

    /**
     *   提供服务
     *   1、调用时机:每一次Servlet被访问时调用
     *   2、调用次数:多次 */
    @Override
    public void service(ServletRequest servletRequest,
     	ServletResponse servletResponse) throws ServletException, IOException {
		// 根据请求方式不同,进行分别的处理
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		// 1. 获取请求方式
		String method = request.getMethod();
		// 2. 判断请求方式
		if("GET".equals(method)){
			// GET 请求方式处理逻辑
		}
		else if("POST".equals(method)){
			// POST 请求方式处理逻辑
		}
    }

    /**
     *   销毁服务
     *   1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁调用
     *   2.调用次数:1次*/
    @Override
    public void destroy() {
    }
    
    /**
    *   获取 ServletConfig 对象*/
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
    *   获取 Servlet信息*/
    @Override
    public String getServletInfo() {
        return null;
    }

}

3、配置:在类上使用@WebServlet注解,配置该Servlet的访问路径

@WebServlet("/资源访问路径")
public class ServletDemo implements Servlet

三、Servlet执行流程&生命周期

1. Servlet由谁创建?Servlet方法有谁调用?

  • Servlet由Web服务器创建,Servlet方法由Web服务器调用

2. 服务器怎么知道Servlet 中一定有service方法?

  • 自定义的Servlet必须实现Servlet接口并复写方法,而Servlet中有service方法

Servlet生命周期

  • 对象的生命周期指一个对象从被创建到被销毁的整个过程
    在这里插入图片描述
  • Servlet运行在Servlet容器(Web服务器)中,其生命周期由容器来管理,分为4个阶段:

①加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象
在这里插入图片描述
可在@WebServlet注解上设置loadOnStartup的值来设定什么时候创建Servlet对象
在这里插入图片描述


②初始化:在Servlet实例化之后,容器将调用Servlet的 init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只会调用一次


③请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求做出处理。


④服务终止:当需要释放内存或容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器将会释放这个Servlet实例,该实例随后会被Java的垃圾收集器回收。

四、Servlet体系结构

在这里插入图片描述

我们在开发B/S架构的Web项目时,都是针对于HTTP协议,所以我们自定义Servlet,会继承HttpServlet

1. 继承HttpServle书写Servlet开发步骤

①继承HttpServlet

②重写doGet和doPost方法(若有其他请求方式,也直接重写即可,例如doPut()doDelete()

@WebServlet("/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    								throws ServletException, IOException {
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
    								throws ServletException, IOException {
    }
}

2. Servlet urlPattern配置

  • Servlet要被访问,必须配置其访问路径(urlPattern)

1. 一个Servlet,可以配置多个urlPattern

@WebServlet(urlPattern = {"/demo1", "/demo2" ...})

2. urlPattern配置规则

① 精确匹配

  • 配置路径
    在这里插入图片描述
  • 访问路径
    在这里插入图片描述

== ② 目录匹配==

  • 配置路径
    在这里插入图片描述
  • 访问路径
    在这里插入图片描述

③ 扩展名匹配

  • 配置路径
    在这里插入图片描述
  • 访问路径
    在这里插入图片描述

④ 任意匹配

  • 配置路径
    在这里插入图片描述
  • 访问路径
    在这里插入图片描述

五、Servlet重要对象

1. Request

Request对象常用方法

Requesst用于获取请求数据。例如:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
								throws ServletException, IOException {
	// 使用 Request 对象获取请求数据
	String name = req.getParameter("name");
}
  • Request对象获取请求行数据的方法:
// 获取请求方式
String getMethod();
// 获取虚拟目录(项目访问路径):/servlet_study
String getContextPath();
// 获取URL(统一资源定位符):http://localhost:8080/servlet_study/demo
StringBuffer getRequestURL();
// 获取URI(统一资源标识符):/servlet_study/demo
String getRequestURI();
// 获取请求参数(GET方式):username=zs&age=18
String getQueryString();
  • Request对象获取请求头数据的方法:
// 根据请求头名称,获取值
String getHeader(String name);
  • Request对象获取请求体数据的方法:
// 获取字节输入流
ServletInputStream getInputStream();
// 获取字符输入流
BufferedReader getReader();

测试代码:

@WebServlet("/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
									throws ServletException, IOException {
        String method = req.getMethod();
        String contextPath = req.getContextPath();
        StringBuffer requestURL = req.getRequestURL();
        String requestURI = req.getRequestURI();
        String queryString = req.getQueryString();
        String userAgent = req.getHeader("user-agent");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
									throws ServletException, IOException {
        // 1. 获取字符输入流
        BufferedReader reader = req.getReader();
        // 2. 读取数据
        String line = reader.readLine();
    }
}

Request通用方式获取请求参数

  • Map<String,String[]> getParameterMap:获取所有参数的Map集合
  • String[] getParameterValues(String name):根据名称获取参数值(数组)
  • String getParameter(String name):根据名称获取参数值(单值)
	// 获取所有参数的Map集合
	Map<String, String[]> map = req.getParameterMap();
	// 根据名称获取参数值(数组)
	String[] values = req.getParameterValues("user");
	// 根据名称获取参数值(单值)
	String value = req.getParameter("user");

请求参数中文乱码问题

  • 请求参数如果存在中文数据,则会乱码。
  • 解决方案:

POST请求:设置输入流的编码

req.setCharacterEncoding("utf-8");

GET请求:转换字符编码形式(先以ISO-8859-1形式编码,再以utf-8形式解码)

// 两种方式
// 1.
String value = req.getParameter("user");
String encode = URLEncoder.encode(value, "ISO-8859-1");
value = URLDecoder.decode(encode, "utf-8");

// 2.
value = new String(value.getBytes(StandardCharsets.ISO_8859_1), 
										StandardCharsets.UTF_8);
// value 就是最终形式正常的数据

请求转发

  • 请求转发(forward):一种在服务器内部的资源跳转方式
    在这里插入图片描述
  • 实现方式
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
								throws ServletException, IOException {
	req.getRequestDispatcher("资源B路径").forward(req, resp);
}
  • 请求转发资源间共享数据:使用Request对象
// 存储数据到 request 域中
void setAttribute(String name, Object o);
// 根据 key,获取值
Object getAttribute(String name);
// 根据 key,删除该键值对
void removeAttribute(String name);
  • 请求转发特点
  • 浏览器地址栏路径不变化
  • 只能转发到当前服务器的内部资源
  • 一次请求,可以在转发的资源间使用 request 共享数据

2. Response

Response对象常用方法

Response用于设置响应数据。例如:

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
									throws ServletException, IOException {
		// 使用 Response对象设置响应数据
		resp.setHeader("content-type", "text/html;charset=utf-8");
		resp.getWriter().write("<h1>hello, world</h1>");
	}
  • Response对象设置响应行数据的方法
// 设置响应状态码
void setStatus(int sc);
  • Response对象设置响应头数据的方法
// 设置响应头键值对
void setHeader(String name, String value);
  • Response对象设置响应体数据的方法
// 获取字符输出流
PrintWriter getWriter();
// 获取字节输出流
ServletOutPutStream getOutPutStream();

Response 响应字符数据

  • 使用

①通过Response对象获取字符输出流

PrintWriter writer = resp.getWriter();

②写数据

writer.write("xxxxxxxxxxxx");

注意:

  • 该流不需要关闭,随着响应结束,response对象销毁,由服务器关闭。

测试代码

protected void doGet(HttpServletRequest request, HttpServletResponse response) 
										throws ServletException, IOException {
	response.setContentType("text/html;charset=utf-8");
	PrintWriter writer = response.getWriter();
	writer.write("<h1>Hello</h1>");
}

Response 响应字节数据

  • 使用

①通过Response对象获取字节输出流

ServletOutputStream outputStream = response.getOutputStream();

②写数据

outputStream.write(字节数据);

测试代码

protected void doGet(HttpServletRequest request, HttpServletResponse response) 
										throws ServletException, IOException {
	// 读取文件
	FileInputStream fis = new FileInputStream("jpg路径");
	// 获取字节输出流
	ServletOutputStream outputStream = response.getOutputStream();
	// 完成流的copy
	byte[] buff = new byte[1024];
	int len = 0;
	while((len = fis.read(buff)) != -1){
		outputStream.write(buff, 0, len);
	}
	fis.close();
}
  • IOUtils工具类使用

①导入坐标

<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.6</version>
</dependency>

②使用

IOUtils.copy(输入流,输出流);

测试代码

protected void doGet(HttpServletRequest request, HttpServletResponse response) 
 										throws ServletException, IOException {
	// 读取文件
	FileInputStream fis = new FileInputStream("jpg路径");
	// 获取字节输出流
	ServletOutputStream outputStream = response.getOutputStream();
	IOUtils.copy(fis, outputStream);
	fis.close();
}

响应数据中文乱码问题

响应数据如果存在中文数据,则会乱码。是因为通过response获取的字符输出流默认编码为ISO-8859-1

  • 解决方案:
// 两种方式:
// 1. 
resp.setContentType("text/html;charset=utf-8");
// 2.
resp.setHeader("contentType", "text/html;charset=utf-8");

重定向

  • 重定向(redirect):一种资源跳转方式
    在这里插入图片描述- 实现方式:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
								throws ServletException, IOException {
	// 1. 设置响应状态码
	resp.setStatus(302);
	// 2. 设置响应头
	resp.setHeader("location", "资源B的路径");
	
	// 或者直接使用下方一句代码
	resp.sendRedirect("资源B的路径");
}
  • 重定向特点
  • 浏览器地址栏路径发生变化
  • 可以重定向到任意位置的资源(服务器内外部均可)
  • 两次请求,不能在多个资源间使用request共享数据

3. 请求转发与重定向路径问题

  • 明确路径使用对象
  • 浏览器使用:需要加虚拟目录(项目访问路径)
  • 服务端使用:不需要加虚拟目录

在请求转发中,是服务器内部完成资源路径的跳转的行为。因此在请求转发中不需要加虚拟目录。

// 不需要加虚拟目录
req.getRequestDispatcher("路径");

重定向是一种服务器指导客户端进行资源路径跳转访问的行为,还是浏览器自身在使用。因此在重定向中需要加虚拟目录。

// 需要加虚拟目录
resp.sendRedirect("路径");

六、Servlet代码优化

  • 在通过继承HttpServlet书写Servlet的类的过程中,通常是一个类只能完成一个需求,这会导致web层的Servlet数量太多,不利于管理与编写。

在这里插入图片描述在这里插入图片描述


  • 解决方法:将Servlet进行归类,对于同一个实体的操作方法,写到一个Servlet中。比如:UserServlet、BrandServlet,并将其urlPattern配置为目录匹配。之后自定义一个BaseServlet类,使用请求路径进行方法分发,替换HttpServlet的根据请求方式进行方法分发
    在这里插入图片描述

  • 在通过继承HttpServlet书写Servlet的类时,如何根据请求方式执行doGet()doPost()是通过HttpServlet中的service()方法确定的。因此我们要完成通过请求路径进行方法分发的需求,需要重写service()方法。

service()方法如图:
在这里插入图片描述

优化步骤

① 自定义BaseServlet类,替换HttpServlet,使用请求路径进行方法分发

/**
 * 替换 HttpServlet, 根据请求路径进行方法分发
 */
public class BaseServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取请求路径
        String uri = req.getRequestURI();
        // 2. 获取方法名 -- 最后一段路径
        String methodName = uri.substring(uri.lastIndexOf('/') + 1);
        // 3. 执行方法
        // 3.1 获取相应实体类Servlet字节码对象
        // 谁调用我(this 所在的方法),this 就代表谁
        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 | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

② 定义实体类Servlet并继承BaseServlet,并将其urlPattern配置为目录匹配

@WebServlet("/brand/*")
public class BrandServlet extends BaseServlet{
  
    public void selectAll(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }

    public void add(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {;
    }

    public void update(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }

    public void delete(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
	// ............................
}

之后在每次的请求中,服务器都会先执行重写的service()方法,根据请求路径进行方法分发。

七、 XML配置方式

  • Servlet从3.0版本开始支持使用注解配置,3.0版本前只支持XML配置方式。
  • 步骤
    ① 编写Servlet类
    ② 在web.xml中配置该Servlet
<servlet>
	<servletr-name>定义的Servlet应用名字</servletr-name>
	<servletr-class>对应的具体Servlet类文件</servletr-class>
</servlet>
//地址映射
<servlet-mapping> 
	<servletr-name>定义的Servlet应用名字</servletr-name>
	<url-pattern>访问路径</url-pattern>
</servlet-mapping>
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值