这里主要参考黑马程序员课程进行学习,网址www.bilibili.com/video/BV1Qf…
Apache Tomcat
简介
Tomcat是Apache软件基金会的一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/jsp少量JavaEE规范
JavaEE: Java Enterprise Edition,Java企业版,指的是Java企业级开发的技术规范总和。包含13项技术规范:JDBC、XML、Servlet等等
基本使用
Tomcat配置
-
配置
- 修改启动的端口号,在路径conf/server.xml
编译后的Java字节码文件和resources的资源文件,放到WEB-INF下的classes目录下
pom.xml中依赖坐标对应的jar包,放入WEB-INF下的lib目录下
IDEA中部署tomcat
集成本地tomcat
使用Tomcat的Maven插件
Servlet
Servlet快速入门
Servlet是Java提供的一门动态web资源开发技术
Servlet是JavaEE规范之一,实际上就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并且由web服务器运行Servlet
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
这里实际上 scope 标签里面必须将其生命周期定义为 provided
provided的生命周期,其作用范围实际上就只在 编译和测试 而在运行时没有,这是由于tomcat之中本身也有servlet协议,有可能冲突
Servlet执行流程
@WebServlet("/demo1")
public class ServletDemo implements Servlet {
}
http://localhost:8080/tomcat_demo1/demo1
这里,我们来解析这个url
其中
http://localhost:8080指向的是web服务器
/tomcat_demo1指向的是我们的web项目本体
/demo1指代的是我们分配的这个servlet的地址
-
Servlet由谁来创建呢?Servlet的方法由谁来调用呢?
- Servlet由web服务器创建,Servlet方法由web服务器调用
-
服务器怎么知道Servlet中一定有service方法?
- 因为我们自定义的Servlet,必须实现Servlet接口并且复写其方法,而Servlet接口中有service方法
Servlet生命周期
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@SuppressWarnings("all")
@WebServlet("/demo1")
public class ServletDemo implements Servlet {
/**
* 初始化方法
* 1、调用时机:默认情况下,当servlet被第一次调用时,调用该方法
* 2、调用次数:only一次
* @param servletConfig
* @throws ServletException
*/
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("service init……");
}
/**
* 1、调用时机:在每一次请求Servlet时
* 2、调用次数:每次请求Servlet时,都会调用一次
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("servlet hello world");
}
/**
* 服务终止,当需要释放内存或者容器关闭。
* 只会调用一次
*/
public void destroy() {
System.out.println("Servlet destroy……");
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
}
这里有一个很重要的点,需要理解:
就是对于Servlet本身而言,我们实际上需要调用的基本上只有
service方法,但是Servlet是一个接口,其中又有以上五个抽象方法,那么也就意味着,只要我们使用Servlet,都需要实现着5个方法。这是相当麻烦的!!!所以这里后来人对其进行了实现,书写了很多实现类,这时候我们只需要来实现对应的实现类,进而来简化操作。
Servlet体系结构
这里我们直接来继承 HttpServlet 类
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/demo4")
public class HttpServletDemo extends HttpServlet {
/**
* 捕获web页面的get请求,然后针对get请求来进行操作
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doget……");
}
/**
* 捕获web页面的post请求,然后针对post请求进行操作
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("do post……");
}
}
这里我们直接来对于源码进行解答:
实际上这里有点玄学:
先把玄学的部分给说了:
- 首先明确 HttpServlet是一个抽象类,
public abstract class HttpServlet extends GenericServlet - 无论是创建Servlet——调用
init()方法,还是监听请求,调用service()方法 ,都是由Web容器(如Tomcat)自动管理的,而不需要编程人员主动编码实现。
在HttpServlet抽象类中的service()方法,实际上它是来识别请求的方法,以此来进行分类是进行get方法处理还是进行post方法处理
接下来看看HttpServlet源码部分
web容器(比如Tomcat)自动识别调用这个service()方法,进入下面HttpServlet的service()方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
//对传入的请求 req 和res进行强制类型转换
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//调用另一个重写的service方法
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
另一个重写的service方法如下:
看下面的这个源码,能够看到,实际上就是根据获得的请求方法 req.getMethod()来判别究竟调用自己写的哪个方法。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
再回到最初始我们自己写的代码,集成抽象类HttpServlet并重载其doGet 和 doPost方法。这样就能够根据我们自己需要的逻辑来进行处理。
urlPattern配置
这里我们直接来看@WebServlet注解的源码即可
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";
String[] value() default {};
String[] urlPatterns() default {};
int loadOnStartup() default -1;
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
针对上面注解的源码,如果看不太懂注解,可以参考我原来另一篇文章 ,Java注解
这里
@Target({ElementType.TYPE})表明这个注解是可以应用于类的任何元素上的@Retention(RetentionPolicy.RUNTIME)这个元注解表明 被修饰的这个注解是运行时有效- 在这里着重介绍
String[] urlPatterns() default {};
实际上我们在使用@WebServlet注解的时候,有时候会这样写 @WebServlet("/demo1") 这里实际上就是使用的是 String[] value() default {}; 它是可以省略的。
如果我们要同步使用别的这里面注解中的“成员变量”最好是使用urlPatterns
这里我们注意到 urlPatterns实际上是一个数组,那么也就意味着可以配置多个路径
比如: @WebServlet(urlPatterns={"/demo4","/demo1"})
另外再了解一下urlPattern配置规则
-
精确匹配 —— 配置路径
@WebServlet("/user/select") -
目录匹配—— 配置路径
@WebServlet("/user/*")- 这时候访问的url可以是 localhost:8080/web_demo/user/aaa 或者是把aaa改成其他任何内容
-
扩展名匹配—— 配置路径
@WebServlet("*.do")-
这时候访问的url可以是
- localhost:8080/web_demo/aaa.do
- localhost:8080/web_demo/bbb.do
-
-
任意匹配—— 配置路径
@WebServlet("/")或者是@WebServlet("/*")
Request&Response
上面这个图实在是画的太好了,我们直接引用过来,然后进行解释说明。
这里首先客户端(浏览器)会向服务器发送请求数据,实际上这些都是字符串,然后请求发送过来,就会被Web容器(比如Tomcat)所解析,解析之后,Tomcat就会把这些请求数据,放到一个对象里面——这个对象正好就是我们要说的request对象,故而request对象之中就会存储一大堆请求数据,故而我们后面编程就会使用request对象来获取对应的请求数据。
通过response对象来设置响应数据,Tomcat在响应给浏览器之前,会先把response对象中的响应数据给取出来,然后拼成符合HTTP响应规则的字符串,然后响应返回给浏览器。
Request获取请求数据
注:只有请求方法为post的时候,请求体中才有内容
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
@WebServlet("/requestDemo")
public class RequestDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
System.out.println(method);
String contextPath = req.getContextPath();
System.out.println(contextPath);
StringBuffer requestURL = req.getRequestURL();
System.out.println(requestURL.toString());
String requestURI = req.getRequestURI();
System.out.println(requestURI);
String queryString = req.getQueryString();
System.out.println(queryString);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BufferedReader reader = req.getReader();
String s = reader.readLine();
System.out.println(s);
}
}
使用postman通过get方法调用一下 http://localhost:8080/tomcat_demo1/requestDemo?name=zhangsan
输出结果为:
GET /tomcat_demo1 http://localhost:8080/tomcat_demo1/requestDemo /tomcat_demo1/requestDemo name=zhangsan
Request通用方法获取请求参数
通过上面的学习,我们实际上可以看到,如果请求是get方法,那么是通过getQueryString()方法来获取请求参数的。如果是post请求,那么就是通过req.getReader() 相关方法来获取参数的。
但是想象一下,如果我现在就传递两个参数比如 name = zhangsan 以及 age =18
无非是通过get请求还是post请求发送的问题,实际上我接收到对应的参数数据之后,进行处理的逻辑是完完全全一样的,但是我却要对于这个玩意儿写两套代码,这是完全不合理的。
▲所以Java贴心的给我们准备了这么一套通用的方法,无论是get请求还是post请求,都可以调用一套通用的方法来进行参数的获取
-
String getParameter(String var1);根据名称获取参数值,单个值 -
Map<String, String[]> getParameterMap();获取所有参数的map集合- 比方说通过get方法传入,是这么传的 xxxxxx?name=zhangsan&money=1000&money=500
- 那么获取到的话就是 key=name ,value=zhangsan 以及 key=money ,value = 1000,500
- 注意到上面值的类型是
String[]哦~
-
String[] getParameterValues(String var1);根据名称获取参数值,数组
实际上,对于以上的方法,我们可以这么简单理解。就是进行了一定程度的封装,然后在底层封装判断是get还是post请求,进而在底层来调用req.getQueryStrint()还是req.getReader()这种东西
那么我们的servlet代码就可以这么写:
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
@WebServlet("/requestDemo")
public class RequestDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Map<String, String[]> parameterMap = req.getParameterMap();
Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
for(Map.Entry<String, String[]> entry:entries){
System.out.println(entry.getKey());
String[] value = entry.getValue();
for(String s:value){
System.out.println(s);
}
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
这里只是一个示例哈。
然后!有一个很奇怪的事儿,我就纳了闷了,我们直接进入req.getParameter的源码,进入的是HttpServletRequest的接口中的抽象方法(毕竟传入的参数req就是HttpServletRequest类型的嘛)
这时候尝试输出: req.getClass()
输出的结果为:class org.apache.catalina.connector.RequestFacade
OK,原来实际上是实现类RequestFacade给我们办的好事儿,做好事儿不留名是这样的。
Request参数中文乱码
详见下面文章:
Request请求转发
请求转发:是一种在服务器内部的资源跳转方式。
请求转发的特点:
- 浏览器地址栏路径不发生变化
- 只能转发到当前服务器的内部资源
- 一次请求可以在转发的资源间使用request共享数据。
这里直接写个小demo来进行演示:
RequestDemo1:
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/requestDemo1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is demo1 do get");
//把一个数据存储进去 键是name 对应的值为zhangsan
req.setAttribute("name","zhangsan");
//将请求转发给当前服务器的另一个地址
req.getRequestDispatcher("/requestDemo2").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is demo1 do post");
this.doGet(req,resp);
}
}
RequestDemo2的代码:
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/requestDemo2")
public class RequestDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is demo2 do get");
Object name = req.getAttribute("name");
System.out.println(name.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is demo2 do post");
this.doGet(req,resp);
}
}
Response设置相应数据
Response完成重定向
关于重定向的路径问题说明:
需要明确路径是谁来使用
- 浏览器使用:需要加虚拟目录(项目访问路径)
- 服务端使用:不需要加虚拟目录
写一个小demo
ResponseDemo1代码如下
package com.itheima.web.response;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/resDemo1")
public class ResponseDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is ResponseDemo1 do get");
//获取项目访问路径
String contextPath = req.getContextPath();
//设置重定向
resp.setStatus(302);
//因为response设置重定向,路径是给客户端用,所以要加上项目访问路径
resp.setHeader("location",contextPath+"/resDemo2");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is ResponseDemo1 do post");
this.doGet(req,resp);
}
}
ResponseDemo2代码如下
package com.itheima.web.response;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/resDemo2")
public class ResponseDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is ResponseDemo2 do get");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("this is ResponseDemo2 do post");
this.doGet(req,resp);
}
}
个人总结
这里我最后还是想写一段自己的感悟,对于自己的理解落实到文字上。
-
首先还是明确,Tomcat是符合JavaEE中的部分规范的,其中这部分规范中就包括了Servlet规范
- 所以这里一定要知道,Tomcat是能够自动监听请求,并且每次有请求过来的时候,就会执行一次
service()方法。(至于怎么监听,怎么“自动的”,这个别管,总之可以)
- 所以这里一定要知道,Tomcat是能够自动监听请求,并且每次有请求过来的时候,就会执行一次
-
Java自己提供了一个
Servlet接口(package javax.servlet;),这里就相当于是Java自动与Tomcat底层代码接上,调用的实际上是这个Servlet接口的service()方法 -
但是呢,这个
Servlet接口里面,有五个方法,也就意味着,我们每次写这个接口的实现类,就必须要实现5个方法。这是没必要的。- 这里顺带提一嘴这个“父子关系”。 Servlet接口<—实现—GenericServlet(抽象类)<—继承—HttpServlet抽象类
-
结合上面父子关系,因为我们很多时候请求都是基于Http协议的,所以Java就最后给了我们一个HttpServlet抽象类。这个HttpServlet抽象类里面最最主要的是两个方法,doGet和doPost
这里我们再回过头来,看一下:
只要请求过来,就会自动调用service()方法;然后再结合Java的多态机制,调用的就会是HttpServlet中的service()方法,然后呢!Tomcat针对HttpServlet抽象类,又有一个实现类RequestFacade。那进而调用的我相信一定是RequestFacade的service()方法。然后我们自个儿再编写自定义类来实现HttpServlet类并重载doGet和doPost方法,就能实现自己的逻辑。至此,完事儿,真厉害啊Java老兄。