目录
Tomcat免费、体积小。tomcat目录结构:
bin:可执行文件目录;conf:配置文件目录;lib:存放lib的目录;logs:日志文件目录;webapps:项目部署的目录;work:工作目录;temp:临时目录。部署在tomcat上的项目,客户端通过网址进行访问,每个项目的context root不一样。
在IDEA中部署web项目的时候,实际上是部署在web文件夹下。实际上部署在服务器上的是artifact包。如果在IDEA中创建一个普通java项目后,需要在模块中添加web,创建artifact包。
一、serverlet获取参数
在网页上输入信息,最终通过服务器上传到数据库,大致思路如下:1,客户端发送请求,action=add; 2,在xml文件中的servlet-mapping中对应的映射关系里,找到add,其对应的servlet-name是addConnection 3,然后找到addConnection对应的类com.myWeb.serverlet.AddServerlet 4,因为发送的是post请求(method=post),tomcat会执行类中的doPost方法;5,最后利用JDBC将数据上传到数据库。
为了使请求得到正确的处理,对WEB-INF文件夹下的web.xml加入如下的servlet代码。
<servlet>
<servlet-name>addConnection</servlet-name>
<servlet-class>com.myWeb.serverlet.AddServerlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>addConnection</servlet-name>
<!--记得一定要加action前面的/-->
<url-pattern>/add</url-pattern>
</servlet-mapping>
前端的网页如下所示:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>fruit</title>
</head>
<body>
<form action="add" method="post">
名称:<input type="text" name="name"/><br/>
数量:<input type="text" name="num"/><br/>
价格:<input type="text" name="price"/><br/>
提交:<input type="submit" value="添加">
</form>
</body>
</html>
处理收到请求的doPost方法如下:
//HttpServlet在tomcat里面,需要对项目添加依赖
public class AddServerlet extends HttpServlet {
@Override
//重写HttpServlet中的doPost方法,处理post请求
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//对于客户端发过来的请求,服务器就将其封装成一个请求req,然后通过getParameter方法获取发送过来的name
String name=req.getParameter("name");
//因为http发送过来的信息只能是String,所以通过包装类的方法进行转换
String numStr=req.getParameter("num");
Integer num=Integer.parseInt(numStr);
String priceStr=req.getParameter("price");
Integer price=Integer.parseInt(priceStr);
//利用JDBC将数据上传到数据库
int count=InsertFruit.insertData(name,num,price);
System.out.println(count);
}
}
Notations:
(1)在配置JDBC的jar包时,不能像普通java项目那样配置,必须配置到WEB-INF下面的lib包。如果lib文件夹不在WEB-INF下,也可以通过ProjectSetting下的Problem点击fix选择add to...。不然jar包实际上没有添加到artifact部署包上。:
(2)部署项目时,修改application context,然后回到server选项卡,需要检查项目运行时默认运行的URL的值,可能会不正确,启动后报错404(找不到指定资源)。如果不修改网址,默认访问的是web下的index html/htm/jsp文件,如果写了其他文件就不行。我们也可以通过web.xml对此进行设置。
(3)如果网页出现405问题,表明当前请求的方法不支持。servlet中必须对应doPost,那么请求方法只能post。
二,汉字乱码
tomcat8之后如果是get请求发送的中文数据不需要设置编码。在tomcat8之前的get请求自身没有设置编码方式的方法,需要把获得的字符串转成byte[]数组,再转UTF-8,最后重新拼接成字符串。对于post请求,需要设置编码方式为UTF-8,就不会出现中文乱码。
//设置req的编码方式
req.setCharacterEncoding("UTF-8");
Notations:设置编码方式需在设置所有参数之前
三、servlet继承关系
关系示意图:
HttpServlet也是一个抽象类,需要继承实现。
常用方法:在servlet接口有:
void init(config) -初始化方法
void service(request,response) -服务方法
vodi destory() -销毁方法
GenericServlet实现了init()和destroy()方法,但GenericServlet中的service方法仍然是抽象的。而在HttpServlet中不再是抽象的。部分service源码:
可见根据请求方式的不同service方法中会调用不同的方法响应。
Notations:当有请求时,service方法会自动响应(实际上由tomcat服务器调用)。在HttpServlet中会分析请求的方式,调用相应的do开头的方法。而这些do方法如果子类没有实现,那么它们会报405错误,如下所示。因此,新建Servlet时,需要考虑请求方法,再决定重写哪个do方法。
四、servlet生命周期
(1)生命周期:从出生到死亡的过程。对应servlet中三个方法:init(),service(),destroy()
(2)默认情况下,第一次请求时,servlet会进行实例化,初始化,然后服务。从第二次请求开始,每一次都只有服务。当容器关闭时,其中所有的servlet实例都会被销毁。这样可以提高系统的启动速度。但是第一次请求时耗时较长。
因此如果要提高系统的启动速度,servlet的l默认情况就可以。但如果要提高响应速度,应该设置servlet的初始化时机。
(3)servlet的初始化时机:可以通过在xml文件的<servlet>标签中加上<load-on-startup>标签来设置初始化的时候。标签中写入一个数值,数值越小时机越靠前(最小值0)。
如:
<servlet-name>test</servlet-name>
<servlet-class>com.myWeb.serverlet.LifeTime</servlet-class>
<load-on-startup>1</load-on-startup>
在第一次请求发送前,就已经初始化了:
servlet在容器中是单例的、线程不安全的。它只会创建一个实例对所有的请求作出响应。线程不安全举例如下:
如果线程1是访问线程,需要num值进行路径判断。而线程2对num值进行修改,可能导致线程1的访问路径改变。
Notations:尽量不要在servlet中定义成员变量。如果定义了,不要去根据变量值进行逻辑判断,不要修改它的值。
五、HTTP协议
Http即超文本传输协议。HTTP是无状态的。HTTP请求响应包括请求和响应两个部分。请求包含三个部分:1,请求行;2,请求消息头;3,请求主体。
Notations:无状态指服务器无法判断发送请求的客户端。
请求行包含三个部分:请求方式(get,post,etc);访问地址;HTTP的协议版本。如下:
POST/dynamic/target.jsp HTTP/1.1
而请求的消息头就是包含了请求的具体参数,包括如下的键值对:
请求体分为三种情况:
(1)get方式没有请求头,但是有一个queryString(查询字符串);
(2)post方式有请求头,form data;
(3)json格式有,叫request playload
响应也包含三个部分,类似的有:1,响应行;2,响应头;3,响应体。
响应行包含三个信息:HTTP协议版本;响应状态码;响应状态。如:
HTTP/1.1 200 ok
Notations:常见的响应状态码:200:正常响应;404:找不到资源;405:请求方式不支持;500:服务器内部错误。
响应头:包含了服务器发送给浏览器的信息(内容媒体信息、编码、内容长度等)
响应体:响应的实际内容。比如请求的html文件里的内容。
六、session会话
由于HTTP是无状态的,会导致发生混乱。比如添加商品到购物车和结账两次请求。而通过会话跟踪技术可以解决这个问题。
(1)会话跟踪技术
如果一个客户端第一次发送请求给服务器,服务器发现没有请求没有会话ID,服务器就会分配一个给它。下次请求服务器就能分辨出这个客户端。常用的API:
request.getSession();
//获取当前会话,没有创建一个新的
request.getSession(false);
//获取当前会话,没有返回NULL,不创建新的,参数为true和不带参数一样
//request获取的对象为session,获取sessionID
session.getID();
//判断当前会话是否是新的
session.isNew();
//获取session的非激活间隔时长,默认半小时
//设置该时长
session.getMaxInactiveInterval();
session.setMaxInactiveInterval();
(2)session保存作用域
当获取到会话对象session后,可以通过setAttribute方法向保存作用域保存一个数据。保存的位置是服务器的内存里。
//保存数据lina,对应的key为uname
session.setAttribute("uname","key");
//获取这个数据
session.getAttribute("uname");
//删除session
session.removeAttribute();
在作用域里存放的数据是跟会话绑定的,当其他客户端进行请求的时候,获取不了这个数据。举例说明:
分别写两个执行service方法的servlet类,并进行配置:
//设置一个作用域
public class Session extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession hs=req.getSession();
hs.setAttribute("key","name");
}
}
//获取上次访问的作用域中保存的数据
public class Session1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getSession().getAttribute("key"));
}
}
//进行配置
<servlet>
<servlet-name>Session</servlet-name>
<servlet-class>com.myWeb.serverlet.Session</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Session</servlet-name>
<!--记得一定要加action前面的/-->
<url-pattern>/session</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Session1</servlet-name>
<servlet-class>com.myWeb.serverlet.Session1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Session1</servlet-name>
<!--记得一定要加action前面的/-->
<url-pattern>/session1</url-pattern>
</servlet-mapping>
使用谷歌浏览器,第一次访问http://localhost:8080/web_war_exploded/session,第二次访问http://localhost:8080/web_war_exploded/session1。可以成功获取到数据。
然后使用IE浏览器访问http://localhost:8080/web_war_exploded/session1,得到的是null,因为更换浏览器导致会话改变。
七、服务器内部转发及客户端重定向
服务器内部转发和客户端重定向使用的代码:
//内部转发
req.getRequestDispatcher("...").forward(req,resp);
//重定向
resp.sendRedirect("...");
服务器内部转发可以把一个请求从一个servlet转发给另一个servlet。如下所示:
客户端重定向,是服务器响应让客户端自己向另一个组件发请求。如下所示:
使用上述两种方式进行相同的操作,服务器内部转发最后客户端只进行了一次请求,而客户端重定向需要客户端发送两次请求。
八、Thymeleaf-视图模板技术
将数据库中获得的数据和静态的网页页面相结合的过程,称之为渲染(render)。Tymeleaf可以帮助进行视图渲染。
可以使用注解为请求配置相应的servlet,不用在web.xml文件中说明,如下所示:
//从servlet3.0开始支持注解方式的注册
@WebServlet("/get")
public class GetServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Fruit> l=new ArrayList<Fruit>();
//利用JDB获得数据库中的数据,并将每行数据封装成一个Fruit类
l=GetFruit.getFruit();
for(Fruit f:l){
System.out.println(f);
}
//创建会话并将Fruit类的ArrayList保存在保存域里面
HttpSession hs=req.getSession();
hs.setAttribute("FruitList",l);
}
}
使用thymeleaf需要添加thymeleaf的jar包。新建的servlet类需要继承ViewBaseServlet。其代码如下:
package com.myWeb.serverlet;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ViewBaseServlet extends HttpServlet {
private TemplateEngine templateEngine;
@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
然后需要在web.xml文件中添加配置,如下所示:
<!-- 在上下文参数中配置视图前缀和视图后缀 -->
<context-param>
<param-name>view-prefix</param-name>
<param-value>/WEB-INF/view/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
<context-parm>叫做配置上下文参数。prefix:前缀;suffix:后缀。第一个param-value也可以写成/,代表根目录WEB-INF。在ViewBaseServlet类中会调用这两个参数。
接着创建一个类继承ViewBaseServlet。因为ViewBaseServlet继承了HttpServlet,所以新类也是一个servlet。然后使用ViewBaseServlet中的processTemplate函数将逻辑视图对应到基础视图上,如下:
//从servlet3.0开始支持注解方式的注册
@WebServlet("/index")
public class GetServlet extends ViewBaseServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Fruit> l=new ArrayList<Fruit>();
//利用JDB获得数据库中的数据,并将每行数据封装成一个Fruit类
l=GetFruit.getFruit();
//创建会话并将Fruit类的ArrayList保存在保存域里面
HttpSession hs=req.getSession();
hs.setAttribute("FruitList",l);
//此处的视图名称是Fruit
//下面的函数会将逻辑视图Fruit名称对应到物理视图名称上去
//物理视图名称是 view-prefix+逻辑视图名称+view-suffix
//而这三者分别对应 / index .html
super.processTemplate("index",req,resp);
}
}
使用thymeleaf进行渲染需要修改index.html页面。thymeleaf的一些常用标签:
th:if th:unless th:each th:text
使用thymeleaf将session中保存的集合渲染到静态页面上:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="2.css">
</head>
<body>
<div id="div_container">
<div id="div_fruit">
<table id="tb_fruit">
<tr>
<th>水果</th>
<th>数量</th>
<th>单价</th>
</tr>
<!--用thymeleaf的语法写的分支语句,没有读到session里的数据就进行合并-->
<tr th:if="${#lists.isEmpty(session.FruitList)}">
<td colspan="3">库存为空</td>
</tr>
<!--设置一个临时变量fruit去获得集合FruitList里面的数据-->
<tr th:unless="${#lists.isEmpty(session.FruitList)}" th:each="fruit:${session.FruitList}">
<td th:text="${fruit.name}">苹果</td>
<td th:text="${fruit.num}">2</td>
<td th:text="${fruit.price}">5</td>
</tr>
</table>
</div>
</div>
</body>
</html>
Notations:<html>标签要加上
xmlns:th="http://www.thymeleaf.org"才能使用thymeleaf;
九、保存作用域
保存的作用域一般有四个:page(基本不用)、request(一次请求响应)、application(整个应用程序)还有之前的session.
session在一次会话里生效,在同一个客户端多次发送请求,都可以获得这个session。而request只在一次请求中生效。比如如果使用客户端重定向,两次请求就获得不了一样的request。
而application在一次应用程序都生效,不同客户端发送请求都可以获得application。获得application的方式:
ServletContext application=req.getServletContext();
ServletContext称为Servlet的上下文环境,当服务启动,它就确定了。
十、路径问题
../表示返回上一级目录。比如上图中,如果user文件夹下的login.html想要引用css文件夹下的login.css文件,使用相对路径:"../css/login.css"。
但在开发中,绝对路径使用较多,例如服务器部署在本机上login.css的绝对路径"http://localhost:8080/pro10/css/index.css"。
可以添加一个基础绝对路径:
<base href="http://localhost:8080/pro10/"/>
//页面上所有路径都以此为基础
//login.css的绝对路径可以改为
<link href="css/login.css">
在thymeleaf中,可以这样写:
th:href="@{}"
//根路径
<link href="@{/css/login.css}">