一、WEB项目的演变
1.发展规律
- 由单机向网络发展
- 由CS向BS发展
2.CS和BS的区别
- CS
-- Client Server
-- 客户端服务器程序
-- 客户端需要单独开发,用户需要单独下载并安装
- BS
-- 浏览器服务器程序
-- 客户端不用单独开发,用户不用单独安装
二、Servlet介绍
1.服务器如何保存并返回一个网页?
- 静态网页
-- 无论谁看其内容都一样
-- 服务器直接存储HTMl,直接返回HTML
- 动态网页
-- 不同的人看到的内容有差异
-- 服务器保存一个组件,动态给每个用户动态生产网页
-- 在Java语言中这个组件就是Servlet
-- 组件:满足规范的对象
2.Servlet的特点
-- 是服务器端的组件
-- 满足sun的规范
-- 可以动态拼接资源(HTML/IMG等)
-- 术语:处理HTTP协议
3.什么是Servlet?
-- 是sun推出的用于在服务器端处理HTTP协议的组件
三、服务器
1.名称
- Java服务器
- WEB服务器
- Java WEB服务器
- Servlet容器
2.本质
- 是一个软件
- 它和浏览器是平级的关系
3.举例
- Tomcat(Apache)
- JBoss
- WebLogic
- WebSphere
四、Tomcat的使用方式
- 绿色版软件,解压缩(释放)可用
1.单独使用(项目上线时)
-- 配置好JAVA_HOME
2.通过Eclipse调用(开发时)
使用:
修改Tomcat端口
- 打开server.xml,在65行修改tomcat的端口,修改后重启
五、Servlet开发步骤
1.创建WEB项目
- 必须具备标准的WEB目录
- /webapp/WEB-INF/web.xml
- eclipse 右击出现Generate Deployment Descriptor Stub自动生成
2.导入jar包
-- 使用maven搜索javaee
-- 在结果中选择javaee-api
-- 使用Tomcat自带的包
-- 选择项目,右键点击properties
-- 弹出框里在左侧选择Targeted Runtimes
-- 在右侧勾选Apache Tomcat
-- Apply
3.开发Servlet
- 编写Servlet
-- 创建package,创建一个类,名为XXXServlet
-- 继承HttpServlet,从而间接实现了
- 配置Servlet
-- 先声明类,并给它取别名--ts
-- 再通过别名引用此类,
部署(拷贝)
-- 在Servers视图下,选择tomcat7
-- 右键点击Add and Remove
-- 在弹出框内将左边的待部署项目移动到右侧
-- 启动tomcat即可
访问
-- 格式:项目名/别名
-- http:localhost:8080/servlet1/ts
web.xml 文件添加的内容
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>web.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/ts</url-pattern>
</servlet-mapping>
项目中的TestServlet类
package web;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
Random rand = new Random();
int num = rand.nextInt(100);
out.println("<h1>"+ num +"</h1>");
out.close();
}
}
效果是在访问中可以看到返回一个随机数,访问路径就是 项目名/别名
获取请求消息头数据,迭代遍历取值
package web;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
System.out.println("请求方式:" + req.getMethod());
System.out.println("请求路径:" + req.getServletPath());
System.out.println("协议类型:" + req.getProtocol());
* 消息头(键值对)
* Enumeration是老版本的迭代器,作用及用法和Iterator类似
Enumeration<String> e = req.getHeaderNames();
while(e.hasMoreElements()) {
String key = e.nextElement();
String value = req.getHeader(key);
System.out.println(key + " : " + value);
}
* 消息头中的大部分数据由服务器填充,但返回的内容格式需要自己指定
res.setContentType("text/html");
PrintWriter out = res.getWriter();
Random rand = new Random();
int num = rand.nextInt(100);
out.println("<h1>"+ num +"</h1>");
out.close();
}
}
打印:
请求方式:GET
请求路径:/ts
协议类型:HTTP/1.1
host : localhost:8080
connection : keep-alive
cache-control : max-age=0
upgrade-insecure-requests : 1
user-agent : Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,**;q=0.8
accept-encoding : gzip, deflate, br
accept-language : zh-CN,zh;q=0.9
cookie : csrftoken=cTqd6QXbuKfzXZt4KqoEGJ1fHVxquYXCxgUQS42DGqH1Q870NgiKZs68KD4R1dAp; pgv_pvi=6176773120; Pycharm-10f6446f=8a4b4f57-683d-4839-bd3d-7b42b1b1a034
解决请求数据的中文乱码
1.get/post
- 将乱码后的字符串,安装ISO还原成byte
- 再将byte按照UTF-8编码为字符串
优点:万能 缺点:麻烦
2.get
- 修改tomcat配置文件
- 在server.xml第65行(不一定是65行),加URIEncoding="utf-8",修改路径传参编码
优点:简单 缺点:只对get有效
修改成如下
<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
3.post
- 在接收参数前,加 req.setCharacterEncoding("utf-8")
- 声明实体内容的编码为utf-8
优点:简单 缺点:只对post有效
一般将第二种和第三种相结合解决get和post方式的乱码
解决返回数据的中文乱码
out.println输出中文时,Servlet默认会使用"iso-8859-1",该编码不支持中文,故不管如何都会乱码
res.setCharacterEncoding("utf-8");
res.setContentType("text/html");
或者写成一句
res.setContentType("text/html;charset=utf-8");
一个注册的小例子
web.xml文件
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class>web.RegServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
</servlet-mapping>
register.html 文件,只需要放在webapp目录下,不需要配置
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="reg" method="post">
<p>账号:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p>
性别:
<input type="radio" name="sex" value="man">
<input type="radio" name="sex" value="woman">
</p>
<p>
兴趣:
<input type="checkbox" name="interest" value="sport">运动
<input type="checkbox" name="interest" value="music">音乐
<input type="checkbox" name="interest" value="movie">电影
</p>
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
请求路径,项目名/register.html
RegServlet.java文件
package web;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RegServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username = " + username + ", password = " + password);
String sex = req.getParameter("sex");
String[] interests = req.getParameterValues("interest"); --- 获取多个数据
System.out.println("sex = " + sex);
if(interests != null) {
System.out.print("interests =");
for(String i:interests) {
System.out.print(" " + i);
}
System.out.println();
}
res.setContentType("text/html;charset=utf-8"); --解决中文乱码
PrintWriter out = res.getWriter();
out.println("<h1>注册成功!!!<h1>");
out.close();
}
}
控制台输出
JavaBean - 满足如下规范的类:
- 有包
- 有默认构造器
- 实现序列化接口
URI和URL的区别
1.狭义
-- 只在Java项目中的URI和URL
-- URI:绝对路径
-- URL:完整路径
> 从表面上看URI包含了URL
2.广义
- 在任意的WEB项目中理解URI和URL
-- URI:资源的名称
-- URL:资源的真名
> URI包含了URL
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
System.out.println("请求方式:" + req.getMethod());
System.out.println("项目名:" + req.getContextPath());
System.out.println("请求的后部分路径:" + req.getServletPath());
System.out.println("URI:" + req.getRequestURI());
System.out.println("URL:" + req.getRequestURL());
}
打印:
请求方式:GET
项目名:/servlet3
请求的后部分路径:/abc
URI:/servlet3/abc
URL:http://localhost:8080/servlet3/abc
重定向
解决一个网站内2个独立组件之间的跳转
一般增加、修改、删除后重定向到查询
res.sendRedirect("[别名]");
建议:
* 1.尽量使用封装类型,因为它比基本类型多了null
* 2.使用java.sql包下的日期,因为JDBC支持这样的日期类型。
设置访问路径的三种方式
所谓的访问路径都是针对部署代码而言的
1.静态资源(HTML/IMG/CSS/JS等)
访问路径就是它在Tomcat内存放的位置
2.动态资源(Servlet)
访问路径就是web.xml中声明的别名
在Servlet中如果没有写返回响应信息,浏览器会看到一片空白,并不会报错。
配置Servlet访问路径:
1.精确匹配
-- 举例:/abc
-- 只有/abc才能访问此Servlet
-- 此Servlet只能处理这一个请求
> 适合规模很小的项目
2.通配符
-- 举例:/*
-- 所有的路径都能访问此Servlet
-- 此Servlet能处理所有u请求
> 适合一个项目只写一个Servlet
3.后缀
-- 举例:*.hi
-- 所有以hi为后缀的请求都能访问此Servlet
-- 此Servlet能处理多个请求
> 适合一个项目写少量的几个Servlet
例子,查询员工,通配路径 *.do,在Servlet中配置特定路径访问
web.xml文件配置匹配路径
<servlet>
<servlet-name>main</servlet-name>
<servlet-class>web.MainServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
Main.Servlet文件
package web;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.EmpDao;
import dao.EmpDaoImpl;
import entity.Emp;
* 路径规范:
* 查询员工:/findEmp.do
* 增加员工:/addEmp.do
public class MainServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String path = req.getServletPath();
if("/findEmp.do".equals(path)) {
findEmp(req,res);
}else if("/addEmp.do".equals(path)) {
addEmp(req,res);
}else {
* 该异常抛给服务器,服务器可以统一处理异常
throw new RuntimeException("找不到了");
}
}
protected void findEmp(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
EmpDaoImpl empdaoimp = new EmpDaoImpl();
List<Emp> list = empdaoimp.findAll();
res.setContentType("text/html;charset=utf-8");
PrintWriter out = res.getWriter();
out.println("<a href=\"addemp.html\">添加员工</a>");
out.println("<table border=\"1\">");
out.println("<td>" + "编号" + "</td>");
out.println("<td>" + "名称" + "</td>");
out.println("<td>" + "工作" + "</td>");
out.println("<td>" + "薪水" + "</td>");
if(list != null) {
for(Emp emp:list) {
out.println("<tr>");
Integer no = emp.getEmpno();
String name = emp.getEname();
String job = emp.getJob();
Double salary = emp.getSalary();
System.out.println("no =" + no + ", name = " + name + ", job = " + job + ", salary = " + salary);
out.println("<td>" + no + "</td>");
out.println("<td>" + name + "</td>");
out.println("<td>" + job + "</td>");
out.println("<td>" + salary + "</td>");
out.println("</tr>");
}
}
out.println("</table>");
out.println("输出结束");
out.close();
}
protected void addEmp(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
String salary = req.getParameter("salary");
System.out.println("username = " + username + ", password = " + password + ", salary = " + salary);
Emp emp = new Emp();
emp.setEname(username);
emp.setJob(password);
if(salary != null && !salary.equals("")) {
emp.setSalary(Double.valueOf(salary));
}
EmpDao dao = new EmpDaoImpl();
dao.save(emp);
res.sendRedirect("findEmp.do");
}
}
HiServlet的生命周期,init(),destroy()
1.默认首次访问Servlet时Tomcat会实例化HiServlet
2.可以改为启动Tomcat时就实例化HiServlet
3.HiServlet从头到尾只实例化一次,所以HiServlet是单例的(一个)
4.每次请求都调用处理执行
5.意义:当需求规定在什么时刻做什么处理时,开发者便于处理
修改实例化Servlet的顺序
<servlet>
<servlet-name>hi</servlet-name>
<servlet-class>web.HiServlet</servlet-class>
<!- 启动时加载此Servlet,中间的数字是加载的顺序。 -->
<load-on-startup>1</load-on-startup>
</servlet>
HiServlet.java文件
package web;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HiServlet extends HttpServlet{
public HiServlet() {
System.out.println("实例化");
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("初始化HiServlet");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
System.out.println("调用HiServlet处理请求");
}
@Override
public void destroy() {
super.destroy();
System.out.println("销毁HiServlet");
}
}
ServletConfig和ServletContext,配置初始化参数
1.作用
使用web.xml做配置文件,使用config或context来读取该文件中的参数
2.区别
- config
-- 和Servlet是1对1的关系
-- Tomcat在初始化每个Servlet前会给它创建一个config
-- 调用HelloServlet.init()前给它创建一个config
-- 如果想给某个Servlet预置数据,使用config
- context
-- 和Servlet是1对多的关系
-- Tomcat在启动时就创建唯一的一个context
-- 所有的Servlet都可以共享这个对象中的数据
-- 如果想给多个Servlet预置数据,使用context
config使用,init()初始化之前创建一个config
web.xml文件
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>web.LoginServlet</servlet-class>
<!- 给此Servlet预置参数 -->
<init-param>
<param-name>maxOnline</param-name>
<param-value>3000</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
读取config参数
LoginServlet.java文件
package web;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet{
* Tomcat创建Servlet的逻辑:
* LoginServlet s = new LoginServlet();
* ServletConfig c = new ServletConfig();
* c.加载数据(); 从web.xml加载数据
* s.init(c);
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String maxOnline = config.getInitParameter("maxOnline");
System.out.println(maxOnline);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
* 此config就是init()传入的那个
ServletConfig cfg = getServletConfig();
System.out.println("正在登录·····"+cfg.getInitParameter("maxOnline"));
}
}
打印: --- 获取到数据了
3000
正在登录·····3000
context使用,读取常量
web.xml 中配置
context:
- 软件内有很多的查询功能,都带有分页
- 每页显示的行数size是常量,并且可配置
- 该数据在多个查询功能之间共用,使用context读取
在标签外配置的参数是给所有Servlet公用的参数, 它们都可以通过context读取该参数
<context-param>
<param-name>size</param-name>
<param-value>20</param-value>
</context-param>
public class TextServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
* Tomcat启动时就会创建唯一的context,并且会调用它的方法
* 加载web.xml中的公用参数,context是全局的,任何Servlet都可以使用
ServletContext ctx = getServletContext();
String size = ctx.getInitParameter("size");
System.out.println(size);
}
}
打印:
20
context可以读写变量
- 用该对象读写的变量是可以被所有的Servlet共用的
- setAttribute()/getAttribute()
web.xml 配置文件
<servlet>
<servlet-name>init</servlet-name>
<servlet-class>web.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>init</servlet-name>
<url-pattern>/init</url-pattern>
</servlet-mapping>
初始化设置变量,在所有的Servlet中都可以读写Context中的变量
* 用来在Tomcat启动时初始化数据,一般WEB项目都有1-2个这样的Servlet。
public class InitServlet extends HttpServlet{
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
* Tomcat启动时会优先创建Context,然后创建Servlet
ServletContext ctx = getServletContext();
ctx.setAttribute("count", 0);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext ctx = getServletContext();
Integer count = (Integer) ctx.getAttribute("count");
ctx.setAttribute("count", ++count);
System.out.println("count = " + count);
}
}
打印:
count = 1
count = 2
count = 3
count = 4
count = 5
Servlet线程安全问题
1.同时修改局部变量
- 局部变量存于栈内
- 每个线程有自己的栈帧
- 此时没有线程安全问题
2.同时修改成员变量
- 成员变量存于堆中
- 所有线程共享这样的数据
- 此时存在线程安全问题
3.解决方案
- 加锁
jsp
jsp本质上就是Servlet
jsp生成Servlet过程
jsp隐含(内置)对象
1.request
- HttpServletRequest
2.response
- HttpServletResponse
3.out
- JSPWriter
- 类似于PrintWriter
4.config
- ServletConfig
5.application
- ServletContext
6.exception
- Throwable
- jsp翻译成的Servlet所抛出的异常
7.session
- HttpSession
8.page
- Object
- 相当于this,用来指代翻译成的那个Servlet
9.pageContext
- PageContext
- 是管理者,通过它可以获得其他8个隐含对象
如何在jsp上使用隐含对象
- <%Object user = session.getAttribute("user"); %>
time.jsp文件
pageEncoding:声明此文档的编码
contentType:声明输出的内容格式
此jsp被其他网页引用
<%@ page pageEncoding="utf-8" contentType="text/html" import="java.text.SimpleDateFormat,java.util.Date"%>
<%
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String now = sdf.format(date);
%>
<p><%=now %></p>
hello.jsp文件,引入上面的time.jsp文件
<%@page pageEncoding="utf-8"%>
<html>
<head>
<meta charset="uft-8">
<title>测试</title>
</head>
<%!
public double bb(double d){
return d*100;
}
%>
<body>
<ul>
<%
for(int i=0; i<10; i++){
%>
<li><%=bb(Math.random()) %></li>
<%
}
%>
</ul>
<!-- 引入一个jsp -->
<%@include file="time.jsp" %>
</body>
</html>
打印:
十个随机数
jsp转发例子
package web;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.EmpDao;
import dao.EmpDaoImpl;
import entity.Emp;
public class FindEmpServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
EmpDao dao = new EmpDaoImpl();
List<Emp> emplist = dao.findAll();
* 转发到jsp,让jsp继续完成这个请求
* 1.将数据绑定到request上
* 2.将请求提交给jsp让它继续处理
* 同时要将request、response给jsp
* 当前:/jsp2/findEmp
* 目标:/jsp2/emp_list.jsp
req.setAttribute("emplist", emplist);
req.getRequestDispatcher("emp_list.jsp").forward(req, res);
}
}
被转发的emp_list.jsp文件
<%@ page pageEncoding="utf-8" import="entity.Emp,java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Insert title here</title>
</head>
<body>
<table border="1">
<tr>
<td>编号</td>
<td>姓名</td>
<td>职位</td>
<td>月薪</td>
</tr>
<%
List<Emp> emplist = (List<Emp>) request.getAttribute("emplist");
if(emplist != null){
for(Emp e:emplist){
%>
<tr>
<td><%=e.getEmpno() %></td>
<td><%=e.getEname() %></td>
<td><%=e.getJob() %></td>
<td><%=e.getSalary() %></td>
</tr>
<%
}
}
%>
</table>
</body>
</html>
MVC
引入了MVC设计模式,对代码分层,降低代码的耦合度。
MVC是经典的设计模式,是代码的分层思想:
M:Model,业务层,用来处理业务
V:View,视图层,用来展现数据
C:Controller,控制层,用来进行调度,是业务层和视图层的桥梁
其目的是要将代码解耦,便于团队开发及维护
转发和重定向
1.转发和重定向的相同点
- 都是解决WEB组件之间的跳转问题
- WEB组件:Servlet/JSP
转发的特点:
1.一次请求
2.地址不变
3.一个请求只有一个request,组件可以通过它共享数据
4.只能转发到项目内部的资源
重定向的特定:
1.二次请求
2.地址改变
3.两个请求有两个request,组件无法通过它共享数据
4.可以重定向到项目外部的资源
建议:
一般是这样
- 查询时用转发
- 增加、删除、修改后重定向到查询
jsp例子
Course.java类
package entity;
import java.io.Serializable;
public class Course implements Serializable{
private Integer courseId;
private String name;
private Integer days;
* 1.与get/set对应的属性叫Bean属性
* 2.通过get/set反应出来的我们以为的属性
* 3.去掉get并将首字母小写的单词是Bean属性
* Bean属性通常和对象属性一致,也可以修改为不一致
public Integer getId() {
return courseId;
}
public void setId(Integer courseId) {
this.courseId = courseId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getDays() {
return days;
}
public void setDays(Integer days) {
this.days = days;
}
}
Student.java类,调用上面的Course
package entity;
import java.io.Serializable;
public class Student implements Serializable{
private String name;
private Integer age;
private String sex;
private String[] interests;
private Course course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests = interests;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
}
FindStudentServlet.java处理逻辑,并转发到jsp
package web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import entity.Course;
import entity.Student;
public class FindStudentServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
Student stu = new Student();
stu.setName("libai");
stu.setAge(23);
stu.setSex("M");
stu.setInterests(new String[] {"a","b","c","d"});
Course c = new Course();
c.setId(1);
c.setName("java");
c.setDays(80);
stu.setCourse(c);
req.setAttribute("stu", stu);
req.getRequestDispatcher("find_student.jsp").forward(req, res);
}
}
find_student.jsp的运算和取值,获取请求中返回的数据
<%@page pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>El和JSTL</title>
</head>
<body>
<h1>El</h1>
<!-- 等价于request.getAttribute("stu").getName() -->
<p>姓名:${stu.name}</p>
<p>年龄:${stu["age"]}</p>
<!-- 等价于request.getAttribute("stu").getCoures().getId() -->
<p>课程:${stu.course.id }</p>
<!-- EL的取值范围:
1.EL默认从如下4个对象中依次取值
page,request,session,application
这4个隐含对象是EL默认的取值范围
2.也可以指定取值范围
requestScope.stu.name
sessionScope.stu.name
3.设计默认取值的目的,是为了开发者不用经常写
范围,是为了简化EL表达式
-->
<p>性别:${requestScope.stu.sex }</p>
<!-- EL支持运算 -->
<p>运算:${stu.age+10 }</p>
<p>是否在某个区间:${stu.age>20 && stu.age<30}</p>
<p>是否为空:${empty stu.age }</p>
<!-- 获取请求参数,我的请求路径为 /jsp3/findStudent?p=123 -->
<p>参数:${param.p }</p>
</body>
</html>
JSTL
maven中导入包
<dependencies>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
其他的文件不变,就修改jsp文件,引入标签库,重命名为c,调用if,choose,forEach方法
<%@page pageEncoding="utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSTL</title>
</head>
<body>
<p>性别:
<c:if test="${stu.sex=='M' }">男</c:if>
<c:if test="${stu.sex=='F' }">女</c:if>
</p>
<p>
性别:
<c:choose>
<c:when test="${stu.sex=='M' }">男</c:when>
<c:otherwise>女</c:otherwise>
</c:choose>
</p>
<!-- items:声明遍历的数据,var:每次循环赋值数据 -->
<p>
兴趣:
<c:forEach items="${stu.interests}" var="interest">
<span>${interest } </span>
</c:forEach>
</p>
</body>
</html>
JSTL标签原理上还是调用类实现的
note
* java.sql包下的日期类型:
* java.sql.Date 年月日
* java.sql.Time 时分秒
* java.sql.Timestamp 年月日时分秒
* 它们都是java.util.Date的子类
一个Dao的例子,注意,其中的数据库连接池的类DBUtils在我之前文章JDBC中有
package dao;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import util.DBUtils;
import entity.Cost;
public class CostDaoImpl implements CostDao,Serializable{
public List<Cost> findAll() {
Connection conn = null;
try {
conn = DBUtils.getConnection();
Statement st = conn.createStatement();
String sql = "select * from zhizhang order by cost_id";
ResultSet rs;
rs = st.executeQuery(sql);
List<Cost> list = new ArrayList<Cost>();
while(rs.next()) {
Cost cost = new Cost();
cost.setCostId(rs.getInt("cost_id"));
cost.setName(rs.getString("name"));
cost.setBaseDuration(rs.getInt("base_duration"));
cost.setBaseCost(rs.getDouble("base_cost"));
cost.setUnitCost(rs.getDouble("unit_cost"));
cost.setStatus(rs.getString("status"));
cost.setDescription(rs.getString("description"));
cost.setCreatime(rs.getTimestamp("creatime"));
cost.setStartime(rs.getTimestamp("startime"));
cost.setCostType(rs.getString("cost_type"));
list.add(cost);
}
return list;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("查询失败", e);
} finally {
DBUtils.close(conn);
}
}
* setInt()/setDouble()不允许传入null,
* 但实际业务中这些字段确实有null的情况,
* 表里也支持null,此时需要将他们当作对象来处理
* 都改成setObject()就行了
public void save(Cost c) {
Connection conn = null;
try {
conn = DBUtils.getConnection();
String sql = "insert into zhizhang (name,base_duration,base_cost,unit_cost,status,description,creatime,startime) "
+ "values(?,?,?,?,?,?,?,NULL)";
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, c.getName());
st.setObject(2, c.getBaseDuration());
st.setObject(3, c.getBaseCost());
st.setObject(4, c.getUnitCost());
st.setString(5, c.getDescription());
st.setString(6, c.getCostType());
st.executeUpdate();
} catch(Exception e) {
e.printStackTrace();
throw new RuntimeException("增加失败");
} finally {
DBUtils.close(conn);
}
}
}
MainServlet.java
package web;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.CostDao;
import dao.CostDaoImpl;
import entity.Cost;
public class MainServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String path = req.getServletPath();
System.out.println("path = " + path);
if("/findCost.do".equals(path)) {
findCost(req, res);
}else if("/toAddCost.do".equals(path)) {
toAddCost(req, res);
}else if("/addCost.do".equals(path)) {
addCost(req, res);
}else if("/toLogin.do".equals(path)){
toLogin(req, res);
}else if("/toIndex.do".equals(path)){
toIndex(req, res);
}else {
throw new RuntimeException("查无此页");
}
}
protected void toLogin(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
req.getRequestDispatcher("WEB-INF/cost/login.jsp").forward(req, res);
}
protected void toIndex(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
req.getRequestDispatcher("WEB-INF/cost/index.jsp").forward(req, res);
}
protected void findCost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
CostDao dao = new CostDaoImpl();
List<Cost> list = dao.findAll();
req.setAttribute("list", list);
req.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);
}
protected void toAddCost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
req.getRequestDispatcher("WEB-INF/cost/add.jsp").forward(req, res);
}
}
自定义错误页面
配置错误页面:
1.tomcat是所有服务端代码调用的入口,它在调用代码时会尝试捕获异常。
2.默认捕获到异常会自动转发到对应的错误页面,如404.html、500.html。
3.我们可以通过配置改变这个默认的行为,明确告诉
tomcat哪个异常去哪个错误页面,这件事本事解决不了异常,仅仅是看起来更友好。
4.声明错误页面时需要写绝对路径,但由于是转发过去,只能访问此项目内部的资源,所以tomcat自动帮我们加上项目名
<!-- 1.通过异常类型配置 -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/error.jsp</location>
</error-page>
<!-- 2.通过异常编号(404/405/500)进行配置 -->
JSTL:(JSP Standard Tag Library)JSP标准标签库
WEB-INF具有保护作用,放在它内部的资源受保护,
无法直接访问,必须通过转发来访问。
1.若jsp没放到WEB-INF下,就可以直接访问它
2.在MVC模式下,直接访问jsp会使其丢失数据来源,导致报错
3.将其放到WEB-INF下,就是包含它不被直接访问,从而避免报错。
静态资源不要放在WEB-INF下。
Cookie
cookie的特点:
- cookie保存在浏览器上
- 多个请求可以共用一组cookie
- 多个Servlet可以共用一组cookie
在/main/login路径下创建的cookie,它只对/main及其下级路径有效
EL默认的取值范围是:
page->request->session->application
EL也可以从cookie中取值,语法为:
cookie.key.value
设置cookie
package web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String username = req.getParameter("username");
* 检查账号密码,通过后将账号存入cookie
* 每个cookie对象只能存一条数据
* key,value都是字符串
* 将cookie发送给浏览器,浏览器接收后会自动保存
Cookie cookie = new Cookie("username", username);
* 声明cookie的生存时间
* >0时cookie保存在客户端的硬盘上
* =0时cookie被浏览器删除
* 默认<0,保存在内存中
cookie.setMaxAge(600000); //秒数
cookie.setPath("/jsp4"); //设置cookie的有效路径,对整个项目都有效
res.addCookie(cookie);
res.setContentType("text/html;charset=utf-8");
PrintWriter out = res.getWriter();
out.println("设置cookie, name = " + cookie.getName() + ", value = " + cookie.getValue());
out.close();
}
}
cookie取值
package web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class IndexServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
* 获取登陆时存入的cookie的值,检测是否过登陆了
PrintWriter out = res.getWriter();
Cookie[] cs = req.getCookies();
if(cs != null) {
res.setContentType("text/html;charset=utf-8");
for(Cookie c:cs) {
out.println(c.getName() + " = " + c.getValue() + "<br>");
}
}
out.close();
}
}
Session
- 浏览器第一次访问服务器时,服务器会给它创建1个session
- 服务器使用cookie将session的SID返回给浏览器
- 浏览器下次访问该服务器时会带上SID
- 多个请求可以共用同一个session
- 多个Servlet可以共用同一个session
* session是存储在服务器上的对象,它的内部可以存储任意类型的数据
String username = req.getParameter("username");
HttpSession session = req.getSession();
session.setAttribute("username", username);
* 响应时服务器会自动创建cookie,将session的ID通过cookie发送给浏览器,类似如下
* Cookie c = new Cookie("JSESSIONID",session.getId());
HttpSession session = req.getSession();
String username = (String)session.getAttribute("username");
如果cookie被禁用了,可以使用URL重写解决问题,也就是把SessionID加到地址后面
过滤器
1.过滤器的作用
- 用来处理项目中的公共需求
- 举例:记录日志、过滤敏感词、权限检查
- 公共的需求:很多请求都包含的业务
2.实现步骤:
- 创建一个类,实现过滤器接口Filter
- 在web.xml中配置这个类
- 过滤器由服务器自动调用
两个简单的业务
public class FindCostServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
System.out.println("查询");
res.setContentType("text/html;charset=utf-8");
PrintWriter out = res.getWriter();
out.println("查询");
out.close();
}
}
public class AddCostServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
System.out.println("添加");
res.setContentType("text/html;charset=utf-8");
PrintWriter out = res.getWriter();
out.println("添加");
out.close();
}
}
一个日志过滤器,在执行各个Servlet之前,之后记录日志
package web;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class LogFilter implements Filter{
* 关闭tomcat时自动调用此方法
public void destroy() {
System.out.println("Log过滤器的销毁方法");
}
* 该方法是处理公共业务的方法
* Filter相当于是Servlet的管家,tomcat在调用Servlet之前会将请求
* 提交给Filter,Filter有权让请求继续,也可以让请求终止
* tomcat就是调用doFilter方法让Filter统一处理请求的,调用它之前会
* 创建好request和response并传入,创建的类型:RequestFacade和ResponseFacade
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Log chain 前");
* 请求跳转到各个Servlet中继续执行
chain.doFilter(request, response);
System.out.println("Log chain 后");
}
* tomcat启动时会自动实例化Filter,然后调用
* 其init()来初始化Filter,调用此方法时会传入config,
* 该对象和Filter是1对1的关系,可以给Filter预置参数(web.xml).
* 该对象和ServletConfig用法完全一样
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Log过滤器的初始化方法");
}
}
检查是否登陆,没有登陆跳转到特定的页面
package web;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginFilter implements Filter{
public void destroy() {
System.out.println("Login过滤器的销毁方法");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Login chain 前");
String[] paths = new String[] {
"/find"
};
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
String path = req.getServletPath();
for(String p:paths) {
if(p.equals(path)) {
chain.doFilter(request, response);
System.out.println("Login chain 后");
return;
}
}
HttpSession session = req.getSession();
String username = (String) session.getAttribute("username");
if(username != null && username != "") { // 没有登陆时,username为null
chain.doFilter(request, response);
System.out.println("Log chain 后");
}else {
res.sendRedirect("/jsp5/find");
}
}
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Login过滤器的初始化方法");
}
}
配置web.xml文件,url-pattern中声明需要过滤的请求
<filter>
<filter-name>log</filter-name>
<filter-class>web.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log</filter-name>
<!-- 声明此Filter可以过滤哪些请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>login</filter-name>
<filter-class>web.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>login</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
最终的结果,当你访问 /jsp5/add (jsp5是我的项目名),会跳转到 /jsp5/find页面
Log chain 前
Login chain 前
查询
Login chain 后
Log chain 后
各个过滤器之间是一层套一层的关系,而他们的执行顺序是按照<filter-mapping>标签的位置来确定的
过滤器上也可以设置特定的参数
<filter>
<filter-name>test</filter-name>
<filter-class>web.TestFilter</filter-class>
<init-param>
<param-name>city</param-name>
<param-value>北京</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>test</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
获取设置的参数
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(filterConfig.getInitParameter("city"));
}
监听器,可以监听HttpSession,HttpRequest,ServletContext三个对象的创建和销毁,还能监听各个对象的增加、删除、替换值的事件
package web;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class MyListener implements ServletRequestListener,ServletRequestAttributeListener {
* tomcat销毁request前自动调用此方法
public void requestDestroyed(ServletRequestEvent e) {
System.out.println("销毁request");
}
* tomcat创建request后自动调用此方法
public void requestInitialized(ServletRequestEvent e) {
System.out.println("创建request");
System.out.println(e.getServletRequest());
}
public void attributeAdded(ServletRequestAttributeEvent arg0) {
System.out.println("向request内添加一个值");
}
public void attributeRemoved(ServletRequestAttributeEvent arg0) {
}
public void attributeReplaced(ServletRequestAttributeEvent arg0) {
}
}
web.xml中添加配置
<listener>
<listener-class>web.MyListener</listener-class>
</listener>
打印结果
创建request
org.apache.catalina.connector.RequestFacade@63e1bdfd
Log chain 前
Login chain 前
查询
Login chain 后
Log chain 后
销毁request