今日内容:
1. web相关概念回顾
2. web服务器软件:Tomcat
3. Servlet入门学习
web相关概念回顾
-
软件架构
- C/S:客户端/服务器端
- B/S:浏览器/服务器端
-
资源分类
- 静态资源:所有用户访问后,得到的结果都是一样的,称为静态资源.静态资源可以直接被浏览器解析
如:html,css,JavaScript - 动态资源:每个用户访问相同资源后,得到的结果可能不一样。称为动态资源。动态资源被访问后,需要先转换为静态资源,在返回给浏览器
如:servlet/jsp,php,asp…
- 静态资源:所有用户访问后,得到的结果都是一样的,称为静态资源.静态资源可以直接被浏览器解析
3. 网络通信三要素
1. IP:电子设备(例如服务器)在网络中的唯一标识。
2. 端口:/*应用程序在计算机中的唯一标识,比如qq有一个端口,一般1024一下的端口被操作系统占用了*/0~65536
3. 传输协议:规定了数据传输的规则
1. 基础协议:
1. tcp:安全协议,因为经过了三次握手。 速度稍慢
2. udp:不安全协议。 速度快
web服务器软件:
* 服务器:安装了服务器软件的计算机,公司用的一般硬件较好
* 服务器软件:接收用户的请求,处理请求,做出响应
* web服务器软件(如tomcat):接收用户的请求,处理请求,做出响应。
* 在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目
* 也被称为web容器,动态资源只有在web容器中才能运行,容器之名的来由
* JavaEE:Java语言在企业级开发中使用的技术规范的总和,一共规定了13项大的规范
* Tomcat:web服务器软件
1. 下载:http://tomcat.apache.org/
2. 安装:解压压缩包即可。
* 注意:安装目录建议不要有中文和空格
3. 卸载:删除目录就行了
4. 启动:
* bin/startup.bat ,双击运行该文件即可
* 访问:浏览器输入:http://localhost:8080 回车访问自己,localhost其实是自己电脑的一个ip(127.0.0.1)的映射,
http://别人的ip:8080 访问别人
* 可能遇到的问题:
1. 黑窗口一闪而过:
* 原因: 没有正确配置JAVA_HOME环境变量
* 解决方案:正确配置JAVA_HOME环境变量
2. 启动报错:
1. 暴力:找到占用的端口号,并且找到对应的进程,杀死该进程
* netstat -ano
2. 温柔:修改自身的端口号
* conf/server.xml
* <Connector port="8888" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8445" />
* 一般会将tomcat的默认端口号修改为80。80端口号是http协议的默认端口号。
* 好处:在访问时,就不用输入端口号
5. 关闭:
1. 正常关闭:
* bin/shutdown.bat
* ctrl+c
2. 强制关闭:
* 点击启动窗口的×
6. 配置:
* 部署项目的方式:
1. 直接将项目放到webapps目录下即可。
* /hello/hello.html:项目的访问路径-->虚拟目录(虚拟目录是什么详情见2.)
* 简化部署:将项目打成一个war包(zip改名),再将war包放置到webapps目录下。
* war包会自动解压缩
2. 配置conf/server.xml文件(很不安全的做法!)
在<Host>标签体中配置
<Context docBase="D:\hello" path="/hehe" />
* docBase:项目存放的路径
* path:虚拟目录,即将项目的路径映射到浏览器上的路径
3. 在conf\Catalina\localhost创建任意名称的xml文件。在文件中编写(best,因为2.还要重启服务器,这是最好的,取消部署只要改文件名不要改配置文件,又称热部署)
<Context docBase="D:\hello" />
* 虚拟目录:xml文件的名称,刚才自己命名的
* 注意path删掉了
* 静态项目和动态项目:
* 目录结构
* java动态项目的目录结构:
-- 项目的根目录
-- WEB-INF目录:
-- web.xml:web项目的核心配置文件
-- classes目录:放置字节码文件的目录
-- lib目录:放置依赖的jar包
将Tomcat集成到IDEA中,并且创建JavaEE的项目,部署项目。
实操:记得虚拟目录在debug/run configuartion里面的deployment里面
下面是tomcat目录一览
Servlet: server applet
* 概念:运行在服务器端的小程序
* Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别和运行)的规则。
* 将来我们自定义一个类,实现Servlet接口,复写方法。
-
快速入门:
1. 创建JavaEE项目
2. 定义一个类,实现Servlet接口* public class ServletDemo1 implements Servlet 3. 实现接口中的抽象方法 4. 配置Servlet 在web.xml中配置: <!--配置Servlet --> <servlet> <servlet-name>demo1</servlet-name> <servlet-class>cn.itcast.web.servlet.ServletDemo1</servlet-class> </servlet> <servlet-mapping> <servlet-name>demo1</servlet-name> <url-pattern>/demo1</url-pattern> </servlet-mapping>
- 执行原理:
-
当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径,即获得虚拟目录的路径。从设定的虚拟目录的根开始
-
查找web.xml文件,是否有对应的标签体内容。如果有看在同一个父元素的servlet name是什么。
-
如果有servlet name,则在找到对应的全类名
-
tomcat会将字节码文件加载进内存,并且创建其对象(反射机制)
-
调用其方法,所以控制台输出hello
-
ps:全类名(即文件在项目中的位置,注意从在idea中从src开始)–>反射机制。
-
- 执行原理:
-
Servlet生命周期
1.**被创建**:执行init方法,只执行一次
-
Servlet什么时候被创建?
* 默认情况下(可以配置执行Servlet的创建的时机),第一次被访问时,Servlet被创建
* 在标签下配置
1.第一次被访问时,创建<load-on-startup>的值为负数 2. 在服务器启动时,创建 <load-on-startup>的值为0或正整数
-
Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的
- 多个用户同时访问时,可能存在线程安全问题。因为多个线程肯定是操作这一个对象
-
解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对修改值
-
- 提供服务:执行service方法,执行多次
- 每次访问Servlet时,Service方法都会被调用一次。
- 被销毁:执行destroy方法,只执行一次
- Servlet被销毁时执行。服务器关闭时,Servlet被销毁
- 只有服务器正常关闭时,才会执行destroy方法。
- destroy方法在Servlet被销毁之前执行,一般用于释放资源
-
IDEA与tomcat的相关配置
-
IDEA会为每一个tomcat部署的项目单独建立一份配置文件,在c盘,部署方法是之前讲过的第二种,指向项目存放的路径。
查看控制台的log:Using CATALINA_BASE:“C:\Users\fqy.IntelliJIdea2018.1\system\tomcat_itcast”
-
工作空间项目 和 tomcat部署的web项目
- tomcat真正访问的是“tomcat部署的web项目”,“tomcat部署的web项目"对应着"工作空间项目” 的web目录下的所有资源,即工作空间更新,tomcat部署的web项目跟着更新(那么这个项目如果和工作空间的不同,那么在哪呢?看1.的配置文件就知道了。看了之后我们发现就在out文件夹下,正是我在idea里面设置的output 目录)
- 可见idea做的工作无非就是1.在CATALINA_BASE路径建配置文件指向实际部署项目和2.让实际部署项目和工作空间项目同步更新)。
- WEB-INF目录下的资源不能被浏览器直接访问。不要把资源放在这个目录下
今日内容:
1. Servlet
2. HTTP协议
3. Request
Servlet:
- 概念
- 步骤
- 执行原理
- 生命周期
- Servlet3.0 注解配置
- Servlet的体系结构
Servlet – 接口
|
GenericServlet – 抽象类
|
HttpServlet – 抽象类
-
GenericServlet:将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象
-
将来定义Servlet类时,可以继承GenericServlet,实现service()方法即可
- HttpServlet(推荐):对http协议的一种封装,简化操作。没有啥稀奇的的,就是封装了一堆if-else的逻辑,http的七种方法都写了个do方法,我们一般关注doGet/doPost.
-
定义类继承HttpServlet
-
复写doGet/doPost方法
-
- HttpServlet(推荐):对http协议的一种封装,简化操作。没有啥稀奇的的,就是封装了一堆if-else的逻辑,http的七种方法都写了个do方法,我们一般关注doGet/doPost.
- Servlet相关配置
- urlpartten:Servlet访问路径
- 一个Servlet可以定义多个访问路径 : @WebServlet({"/d4","/dd4","/ddd4"})
- 路径定义规则:
- /xxx:路径匹配
- /xxx/xxx:多层路径,目录结构
- *.do(通配符,老朋友了,注意优先级的问题,通配还是和以前一样很低):扩展名匹配,do是自定义的资源后缀
- urlpartten:Servlet访问路径
HTTP:
-
概念:Hyper Text Transfer Protocol 超文本传输协议
-
传输协议:定义了客户端和服务器端通信时,发送数据的格式
-
特点:
- 基于TCP/IP的高级协议
- 默认端口号:80 即http://www.itcast.cn=http://www.itcast.cn:80,前面只是ip地址,理论上本来还要,:端口号,只是端口号为80的时候可以不写,因为默认80
- 基于请求/响应模型的:即一次请求对应一次响应,请求次数=响应次数
- 无状态的:每次请求之间相互独立,不能交互数据
- 历史版本:
- 1.0:每一次请求响应(注意打开网页这个操作有多次请求,因为每个资源都是一次请求,如一个图片一次请求)都会建立新的连接
- 1.1:复用服务器和客户端的连接,每次请求后等一会看看还有没有紧接的请求
-
-
请求消息数据格式
-
请求行
请求方式 请求url 请求协议/版本
GET /login.html HTTP/1.1- 请求方式:
- HTTP协议有7中请求方式,常用的有2种
- GET:(访问一个网站是默认GET方法,提交表单可以选择get/post)
- 请求参数在请求行中,在url后。(即可以观察到浏览器上面的url的变化)
- 请求的url长度有限制的(不利于文件,太大需要post)
- 相对不太安全(因为体现在url里了)
- POST:
- 请求参数在请求体中
- 请求的url长度没有限制的
- 相对安全
- GET:(访问一个网站是默认GET方法,提交表单可以选择get/post)
- HTTP协议有7中请求方式,常用的有2种
- 请求方式:
-
请求头:客户端浏览器告诉服务器一些信息
请求头名称: 请求头值(键:值)- 介绍一下两个常用的请求头的键值:
- User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
-
可以在服务器端获取该头的信息,解决浏览器的兼容性问题
-
Referer:http://localhost/login.html
-
告诉服务器,我(当前请求)从哪里来?(一般在servlet类里面获得它,注意如果直接通过路径访问这个类的referer是null,必须要跳转到某个servlet类中获得跳转前路径)
- 作用(作用如下图,左边代码为统计工作,右边为防盗链的代码):
-
防盗链:
-
统计工作:
-
- 作用(作用如下图,左边代码为统计工作,右边为防盗链的代码):
-
-
-
请求空行
就是空行,用于分割POST请求的请求头,和请求体的。 -
请求体(正文):
- 封装POST请求消息的请求参数(GET方法没有请求体,因为请求参数在url中)
-
请求消息的字符串格式:
POST /login.html HTTP/1.1(请求行)
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1(请求头,客户端给客户端的信息)(请求空行)
username=zhangsan(请求参数)
-
-
响应消息数据格式
Request:
-
request对象和response对象的原理
- request和response对象是由服务器创建的,自动传给service方法。我们来使用它们而已
- request对象是来获取请求消息,response对象是来设置响应消息
-
request对象继承体系结构:
ServletRequest – 接口
| 继承
HttpServletRequest – 接口(还记得httpservlet接口吗对应这个)
| 实现
org.apache.catalina.connector.RequestFacade 类(tomcat创建的对象,在tomcat源码中可以找到这个类) -
request功能:
- 获取请求消息数据(不是request对象唯一功能噢)
-
获取请求行数据
- GET /day14/demo1?name=zhangsan HTTP/1.1
- 方法:
-
获取请求方式 :GET
- String getMethod()
-
(*****)获取虚拟目录:/day14
- String getContextPath()
-
获取Servlet路径(区别一下虚拟目录,这个加虚拟目录=URI): /demo1
- String getServletPath()
-
获取get方式请求参数:name=zhangsan
- String getQueryString()
-
(*****)获取请求URI:/day14/demo1
-
String getRequestURI(): /day14/demo1
-
StringBuffer getRequestURL() :http://localhost/day14/demo1,URL更加长
-
URL:统一资源定位符 : http://localhost/day14/demo1
-
URI:统一资源标识符 : /day14/demo1 和URL关系=共和国和中华人民共和国的关系
-
-
获取协议及版本:HTTP/1.1
- String getProtocol()
-
获取客户机的IP地址:
- String getRemoteAddr()
-
-
获取请求头数据
- 方法:
- (*)String getHeader(String name):通过请求头的名称获取请求头的值
- Enumeration getHeaderNames():获取所有的请求头名称(Enumeration类似迭代器类)
- 回忆一下请求头里面关注的两个键值对:referer和user-agent
- ps:插一句话,idea怎么同时启动两个项目,必须要在debug/run Configraution里面再创建一个tomcat并改变两个端口号,最后两个tomcat启动即可。
- 方法:
-
获取请求体数据:
-
请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数。参数被封装成流的格式
-
步骤:
- 获取流对象
-
BufferedReader getReader():获取字符输入流,只能操作字符数据
-
ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据
-
2.再从流对象中拿数据。带高级readline方法的缓冲流用readline
- 获取流对象
-
-
其他功能:
- 获取请求参数通用方式(常用这个种方法获得请求参数,而不是通过URI和请求体):不论get还是post请求方式都可以使用下列方法来获取请求参数
- String getParameter(String name):根据参数名称获取参数值 username=zs&password=123
- String[] getParameterValues(String name):根据参数名称获取参数值的数组 hobby=xx&hobby=game
- Enumeration getParameterNames():获取所有请求的参数名称
- Map<String,String[]> getParameterMap():获取所有参数的map集合
- 中文乱码问题:
- get方式:tomcat 8 已经将get方式乱码问题解决了
- post方式:会乱码
- 解决:在获取参数前,**设置request的编码request.setCharacterEncoding(“utf-8”);**因为post参数被封装成了流的格式,所以需要设置一下编码
-
请求转发:一种在服务器内部的资源跳转方式
-
步骤:
- 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)(path是转发的目的地)
- 使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response)
-
特点:
- 浏览器地址栏路径不发生变化
- 只能转发到当前服务器内部资源中。
- 转发是一次请求,不管转发多少次都属于一次请求,这和特点1吻合
-
-
共享数据:
- 域对象:一个有作用范围的对象,可以在范围内共享数据
- request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
- 方法:
- void setAttribute(String name,Object obj):存储数据
- Object getAttitude(String name):通过键获取值
- void removeAttribute(String name):通过键移除键值对
-
获取ServletContext:
- ServletContext getServletContext()
- 获取请求参数通用方式(常用这个种方法获得请求参数,而不是通过URI和请求体):不论get还是post请求方式都可以使用下列方法来获取请求参数
-
- 获取请求消息数据(不是request对象唯一功能噢)
案例:用户登录
-
用户登录案例需求:
1.编写login.html登录页面
username & password 两个输入框
2.使用Druid数据库连接池技术,操作mysql,day14数据库中user表
3.使用JdbcTemplate技术封装JDBC
4.登录成功跳转到SuccessServlet展示:登录成功!用户名,欢迎您
5.登录失败跳转到FailServlet展示:登录失败,用户名或密码错误-
分析
-
开发步骤
-
创建项目,导入html页面,配置文件,jar包
-
创建数据库环境
CREATE DATABASE day14;
USE day14;
CREATE TABLE USER(id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) UNIQUE NOT NULL, PASSWORD VARCHAR(32) NOT NULL
);
-
创建包cn.itcast.domain,创建类User
package cn.itcast.domain;
/**-
用户的实体类
*/
public class User {private int id;
private String username;
private String password;public int getId() {
return id;
}public void setId(int id) {
this.id = id;
}public String getUsername() {
return username;
}public void setUsername(String username) {
this.username = username;
}public String getPassword() {
return password;
}public void setPassword(String password) {
this.password = password;
}@Override
public String toString() {
return “User{” +
“id=” + id +
“, username=’” + username + ‘’’ +
“, password=’” + password + ‘’’ +
‘}’;
}
}
-
-
创建包cn.itcast.util,编写工具类JDBCUtils
package cn.itcast.util;import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;/**
-
JDBC工具类 使用Durid连接池
*/
public class JDBCUtils {private static DataSource ds ;
static {
try { //1.加载配置文件 Properties pro = new Properties(); //使用ClassLoader加载配置文件,获取字节输入流 InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); //2.初始化连接池对象 ds = DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }
}
/**
- 获取连接池对象
*/
public static DataSource getDataSource(){
return ds;
}
/**
- 获取连接Connection对象
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
- 获取连接池对象
-
-
创建包cn.itcast.dao,创建类UserDao,提供login方法
package cn.itcast.dao;
import cn.itcast.domain.User;
import cn.itcast.util.JDBCUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;/**
-
操作数据库中User表的类
*/
public class UserDao {//声明JDBCTemplate对象共用
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());/**
-
登录方法
-
@param loginUser 只有用户名和密码
-
@return user包含用户全部数据,没有查询到,返回null
*/
public User login(User loginUser){
try {
//1.编写sql
String sql = “select * from user where username = ? and password = ?”;
//2.调用query方法
User user = template.queryForObject(sql,
new BeanPropertyRowMapper(User.class),
loginUser.getUsername(), loginUser.getPassword());return user;
} catch (DataAccessException e) {
e.printStackTrace();//记录日志
return null;
}
}
}
-
-
-
编写cn.itcast.web.servlet.LoginServlet类
package cn.itcast.web.servlet;import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;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("/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置编码
req.setCharacterEncoding(“utf-8”);
//2.获取请求参数
String username = req.getParameter(“username”);
String password = req.getParameter(“password”);
//3.封装user对象
User loginUser = new User();
loginUser.setUsername(username);
loginUser.setPassword(password);
//4.调用UserDao的login方法
UserDao dao = new UserDao();
User user = dao.login(loginUser);
//5.判断user
if(user == null){
//登录失败
req.getRequestDispatcher("/failServlet").forward(req,resp);
}else{
//登录成功
//存储数据
req.setAttribute(“user”,user);
//转发
req.getRequestDispatcher("/successServlet").forward(req,resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
7. 编写FailServlet和SuccessServlet类
@WebServlet("/successServlet")
public class SuccessServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取request域中共享的user对象
User user = (User) request.getAttribute("user");
if(user != null){
//给页面写一句话
//设置编码
response.setContentType("text/html;charset=utf-8");
//输出
response.getWriter().write("登录成功!"+user.getUsername()+",欢迎您");
}
}
@WebServlet("/failServlet")
public class FailServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//给页面写一句话
//设置编码
response.setContentType("text/html;charset=utf-8");
//输出
response.getWriter().write("登录失败,用户名或密码错误");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
- login.html中form表单的action路径的写法
- 虚拟目录+Servlet的资源路径,不就是要提交的Servlet类所在路径吗
- BeanUtils工具类,简化数据封装
- 用于封装JavaBean的
-
JavaBean:标准的Java类
- 要求:
- 类必须被public修饰
- 必须提供空参的构造器
- 成员变量必须使用private修饰
- 提供公共setter和getter方法
- ps:可见我们写的User类完全满足这些标准
- 功能:封装数据
- 要求:
-
概念:区分属性和成员变量
成员变量:
属性:是setter和getter方法名字截取后并将首字母小写后的产物
例如:getUsername() --> Username–> username可见成员变量名不等于属性名,虽然我们总是让两者相等,不相等会有点问题,详情可见login_page代码里面的一个测试代码
-
方法(三个方法都是操作属性,属性进而影响成员变量的值):
-
setProperty()为属性赋值
-
getProperty()得到属性的值
-
populate(Object obj , Map map):将map集合的键值对信息,封装到对应的JavaBean对象的属性中。由上面红字可知,我们需要保证 成员变量名属性名map的key==页面传来的参数名
-
ps:今天下午打开别人的代码打不开,但是自建工程后再复制进来就好了,推测是配置问题,所以把自建工程artifacts的配置截个图
今日内容
1. HTTP协议:响应消息
2. Response对象
3. ServletContext对象
HTTP协议:
1. 请求消息:客户端发送给服务器端的数据
* 数据格式:
1. 请求行
2. 请求头
3. 请求空行
4. 请求体
2. 响应消息:服务器端发送给客户端的数据,类似相应消息
* 数据格式:
1. 响应行
1. 组成:协议/版本 响应状态码 状态码描述
2. 响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态。
1. 状态码都是3位数字
2. 分类:
1. 1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx状态码询问客户端还有消息没
2. 2xx:成功。例如:200
3. 3xx:重定向。例如:302(重定向),304(访问缓存)
4. 4xx:客户端错误。
* 例如:
* 404(请求路径没有对应的资源)
* 405:请求方式没有对应的doXxx方法
5. 5xx:服务器端错误。例如:500(服务器内部出现异常)
2. 响应头:
1. 格式:头名称: 值
2. 常见的响应头:
1. Content-Type(html代码里面也有这个属性):服务器告诉客户端本次响应体(例如HTML)数据格式以及编码格式
2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据
* 值:
* in-line:默认值,在当前页面内打开
* attachment;filename=xxx:以文件形式打开响应体。应用于文件下载,此时肯定响应体不是html代码
3. 响应空行
4. 响应体:传输的数据(例如HTML代码但不止于,还有图片等)
* 响应字符串格式
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 101
Date: Wed, 06 Jun 2018 07:08:42 GMT
<html>
<head>
<title>$Title$</title>
</head>
<body>
hello , response
</body>
</html>
Response对象
* 功能:设置响应消息
1. 设置响应行
1. 格式:HTTP/1.1 200 ok
2. 设置状态码:setStatus(int sc)
2. 设置响应头:setHeader(String name, String value)
3. 设置响应体:
* 使用步骤:
1. 获取输出流
* 字符输出流:PrintWriter getWriter()
* 字节输出流:ServletOutputStream getOutputStream()
2. 使用输出流,将数据输出到客户端浏览器
-
案例:
- 完成重定向(案例)
-
重定向:资源跳转的方式
-
代码实现:
//1. 设置状态码为302
response.setStatus(302);
//2.设置响应头location
response.setHeader(“location”,"/day15/responseDemo2"); -
图示如下
//很明显其实只有路径可以变,所以提供了简单的重定向方法
response.sendRedirect("/day15/responseDemo2");
-
- 完成重定向(案例)
-
重定向的特点(和转发完全相反):redirect
-
地址栏发生变化
-
重定向可以访问其他站点(服务器)的资源
-
重定向是两次请求。不能使用request对象来共享数据
-
转发的特点:forward
- 转发地址栏路径不变
- 转发只能访问当前服务器下的资源
- 转发是一次请求,可以使用request对象来共享数据
-
路径分类
- 相对路径:通过相对路径不可以确定唯一资源
- 如:./index.html
-
不以/开头,以.开头路径
* 规则:找到当前资源和目标资源之间的相对位置关系 * ./:当前目录(./是可以省略的) * ../:后退一级目录
-
绝对路径:通过绝对路径可以确定唯一资源
- 如:http://localhost/day15/responseDemo2 /day15/responseDemo2(so转发和重定向其实都是使用绝对路径,因为以/开头,只是省略了localhost之前内容,转发又因为只会在服务器资源内转,所以把虚拟目录也省略了)
-
-
以/开头的路径,和相对相反,
* 规则:判断定义的路径是给谁用的? **给客户端浏览器使用:需要加虚拟目录**(项目的访问路径,很正常,因为这些都可以**跳转到服务器外,当然需要虚拟目录**) 建议虚拟目录**动态获取**(防止改变虚拟目录后的影响):写成如下字符串 request.getContextPath()+“Servlet的路径” **例如前端的标签(这个路径给客户端来跳转),重定向(这个路径给客户端来去找新的定向)**.. **给服务器使用:不需要加虚拟目录** **例如转发路径**
- 相对路径:通过相对路径不可以确定唯一资源
-
-
-
服务器输出字符数据到浏览器(案例)
-
步骤:
- 获取字符输出流(获取而非创建,流向已经指定为客户端)
- 输出数据
-
注意:
-
乱码问题的唯一原因:编码集不等于解码集
-
PrintWriter pw = response.getWriter();获取的流的默认编码是ISO-8859-1,因为不是中国人写的tomcat
-
怎么解决?灵活的方法:
response.setHeader(“content-type”,“text/html; charset=utf-8”)
即通过设置响应头里面的键值对来告诉浏览器用什么编码来解码
-
//上面方法的简化,省略第一个不变的参数,设置编码,是在获取流之前设置
response.setContentType(“text/html;charset=utf-8”); -
-
-
服务器输出字节数据到浏览器(案例)
* 步骤:
1. 获取字节输出流(同上理,记得设置编码)
2. 输出数据- 验证码(案例)
-
本质:图片
-
目的:防止恶意表单注册
-
- 验证码(案例)
ServletContext对象:
- 概念:代表整个web应用,可以和程序的容器(服务器)来通信
- 获取:
- 通过request对象获取
request.getServletContext(); - 通过HttpServlet获取
this.getServletContext();
- 通过request对象获取
- 该对象的重要方法和功能:
- 获取MIME类型:
- MIME类型:在互联网通信过程中定义的一种文件数据类型。是不是很眼熟,就是响应体里面数据的类型,可以这么说响应体里面的数据就是MIME类型的数据
- 格式: 大类型/小类型 text/html image/jpeg
- MIME类型:在互联网通信过程中定义的一种文件数据类型。是不是很眼熟,就是响应体里面数据的类型,可以这么说响应体里面的数据就是MIME类型的数据
- 获取MIME类型:
- 动态获取文件类型:String getMimeType(String file)
-
域对象:共享数据,类似response对象
- setAttribute(String name,Object value)
- getAttribute(String name)
-
removeAttribute(String name)
- ServletContext对象范围:所有用户所有请求的数据
-
获取文件的真实(服务器)路径(通过idea启动时候的CATALINA_BASE找到配置文件,通过配置文件找到真实部署在tomcat的项目的位置,观察该项目位置得到下面的绝对路径,不要看工作空间的,这点可参考tomcat第一次笔记也有记载)
方法:String getRealPath(String path) (很重要的方法,转工作空间路径为真实路径,context对象由ServletContext对象获得)
String b = context.getRealPath("/b.txt");//web目录下资源访问
String c = context.getRealPath("/WEB-INF/c.txt");//WEB-INF目录下的资源访问
String a = context.getRealPath("/WEB-INF/classes/a.txt");//src目录下的资源访问
-
案例:
* 文件下载需求:
1. 页面显示超链接
2. 点击超链接后弹出下载提示框
3. 完成图片文件下载
-
分析:
- 如果在html里面用超链接:超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框。不满足需求
- 我们实际需求:任何资源都必须弹出下载提示框
- 使用响应头设置资源的打开方式:
- content-disposition:attachment;filename=xxx
-
步骤:
- 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename作为本次请求的参数,即请求体
- 定义Servlet
- 获取文件名称
- 使用字节输入流加载文件进内存(通俗地将,利用一个buff数组作为输入流到输出流之间地桥梁)
- 指定response的响应头: content-disposition:attachment;filename=xxx
- 将数据写出到response输出流(自然是字节流而不是字符流)
- 浏览器接受到字节信息,然后根据响应头的键值对指定的类型来解析这个响应体,进而弹出下载提示框。其他类型的响应体都是同理,这种巧妙方式使得可以响应各种不同的数据给客户端
- 问题:
- 中文文件问题
- 解决思路:
- 获取客户端使用的浏览器版本信息(通过请求头的键值对)
- 根据不同的版本信息,设置filename的编码方式不同(通过工具类)
- 解决思路:
- 中文文件问题
今日内容
1. 会话技术
1. Cookie
2. Session
2. JSP:入门学习
会话技术
- 会话:一次会话中包含多次请求和响应。
- 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止
- 功能:在一次会话的范围内的**!多次请求间!共享数据**,不然根据http协议请求间是独立的
- 方式:
- 客户端会话技术:Cookie,共享数据在客户端
- 服务器端会话技术:Session,共享数据在服务端
Cookie:
-
概念:客户端会话技术,将数据保存到客户端。为什么说是保存在客户端,因为换一个浏览器就访问不到了
-
快速入门:
- 使用步骤:
- 创建Cookie对象,绑定数据
- new Cookie(String name, String value)
- 发送Cookie对象
- response.addCookie(Cookie cookie)
- 获取Cookie,拿到数据
- Cookie[] request.getCookies()
- 创建Cookie对象,绑定数据
- 使用步骤:
-
实现原理
- 基于响应头set-cookie和请求头cookie实现
- 回顾一下,浏览器访问一个Servlet类就是等效发送了一个请求,通过requset对象,而response对象就是Servlet类做出响应的载体,设置这个对象就是设置响应的信息,这个响应和请求都是自动的,我们只要改变对象即可
-
cookie的细节
- 一次可不可以发送多个cookie?
- 可以
- 可以创建多个Cookie对象,使用response调用多次addCookie方法发送cookie即可。
- cookie在浏览器中保存多长时间?
- 默认情况下,当浏览器关闭后,Cookie数据被销毁
- 持久化存储:
- setMaxAge(int seconds)
- 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,以秒为单位,时间到后,cookie文件自动失效
- 负数:默认不持久化存储,关闭当前页面即删除
- 零:删除cookie信息,等效删除键值对操作
- setMaxAge(int seconds)
- cookie能不能存中文?
- 在tomcat 8 之前 cookie中不能直接存储中文数据。
- 需要将中文数据转码—一般采用URL编码(%E3)
- 在tomcat 8 之后,cookie支持中文数据。特殊字符还是不支持,建议使用URL编码存储,URL解码解析,比如空格。实际上看很多网站的URI可以发现他们中文也还是用的URL编码
- 在tomcat 8 之前 cookie中不能直接存储中文数据。
- cookie共享问题?
- 假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?
-
默认情况下cookie不能共享
-
cookie.setPath(String path):设置cookie的获取范围。默认情况下,设置当前的虚拟目录,如果要共享,则可以将path设置为"/"
2.不同的tomcat服务器间cookie共享问题?
setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享,path:**可以共享的项目的一级域名** setDomain(".baidu.com"),那么tieba.baidu.com和news.baidu.com中cookie可以共享
-
- 假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?
- 一次可不可以发送多个cookie?
-
Cookie的特点和作用
- cookie存储数据在客户端浏览器,浏览器的设置里面都能看到
- 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)
- 作用:
- cookie一般用于存出少量的不太敏感的数据
- 在不登录的情况下,完成服务器对客户端的身份识别,通过客户端保存的cookie,比如浏览器的设置选项,都没有账号时设置的,所以只能在cookie中保存这些信息
-
案例:记住上一次访问时间
-
需求:
- 访问一个Servlet,如果是第一次访问,则提示:您好,欢迎您首次访问。
- 如果不是第一次访问,则提示:欢迎回来,您上次访问时间为:显示时间字符串
-
分析:
- 可以采用Cookie来完成
- 在服务器中的Servlet判断是否有一个名为lastTime的cookie
-
有:不是第一次访问
- 响应数据:欢迎回来,您上次访问时间为:2018年6月10日11:50:20
- 写回Cookie:lastTime=2018年6月10日11:50:01
-
没有:是第一次访问
- 响应数据:您好,欢迎您首次访问
- 写回Cookie:lastTime=2018年6月10日11:50:01
-
注意c ookie.setValue
-
-
3.代码实现:
package cn.itcast.cookie;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
@WebServlet("/cookieTest")
public class CookieTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应的消息体的数据格式以及编码
response.setContentType("text/html;charset=utf-8");
//1.获取所有Cookie
Cookie[] cookies = request.getCookies();
boolean flag = false;//没有cookie为lastTime
//2.遍历cookie数组
if(cookies != null && cookies.length > 0){
for (Cookie cookie : cookies) {
//3.获取cookie的名称
String name = cookie.getName();
//4.判断名称是否是:lastTime
if("lastTime".equals(name)){
//有该Cookie,不是第一次访问
flag = true;//有lastTime的cookie
//设置Cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date = sdf.format(date);
System.out.println("编码前:"+str_date);
//URL编码
str_date = URLEncoder.encode(str_date,"utf-8");
System.out.println("编码后:"+str_date);
cookie.setValue(str_date);
//设置cookie的存活时间
cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
response.addCookie(cookie);
//响应数据
//获取Cookie的value,时间
String value = cookie.getValue();
System.out.println(“解码前:”+value);
//URL解码:
value = URLDecoder.decode(value,“utf-8”);
System.out.println(“解码后:”+value);
response.getWriter().write(“
欢迎回来,您上次访问时间为:”+value+"
");
break;
}
}
}
if(cookies == null || cookies.length == 0 || flag == false){
//没有,第一次访问
//设置Cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy年MM月dd日 HH:mm:ss”);
String str_date = sdf.format(date);
System.out.println(“编码前:”+str_date);
//URL编码
str_date = URLEncoder.encode(str_date,“utf-8”);
System.out.println(“编码后:”+str_date);
Cookie cookie = new Cookie(“lastTime”,str_date);
//设置cookie的存活时间
cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
response.addCookie(cookie);
response.getWriter().write("<h1>您好,欢迎您首次访问</h1>");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
JSP:入门学习
1. 概念:
* Java Server Pages: java服务器端页面
* 可以理解为:一个特殊的页面,其中既可以指定定义html标签,又可以定义java代码
* 用于简化Servlet的response.write方法的书写!!!,不然我想响应一个页面,那个参数岂不是有几百行啊
-
原理
- JSP本质上就是一个Servlet,可以在tomcat的work目录下找到它,它的本质就是自动转换成一个已经帮我们写好的Servlet类,实质上还是调用write方法做出响应消息而已
-
JSP的脚本:JSP定义Java代码的方式
-
<% 代码 %>:定义的java代码,在service方法中。service方法中可以写什么,该脚本中就可以写什么。
-
<%! 代码 %>:定义的java代码,在jsp转换后的Servlet类的成员位置。
-
<%= 代码 %>:定义的java代码,会输出到页面上。输出语句中可以定义什么,该脚本中就可以定义什么。
-
JSP的内置对象:
- 在jsp页面中不需要获取和创建,可以直接使用的对象
- jsp一共有9个内置对象。
- 今天学习3个:
- request
- response
- out:字符输出流对象。可以将数据输出到页面上。和response.getWriter()类似
- response.getWriter()和out.write()的区别:
- 在tomcat服务器真正给客户端做出响应之前,会先找response缓冲区数据,再找out缓冲区数据。
- response.getWriter()数据输出永远在out.write()之前
- response.getWriter()和out.write()的区别:
-
案例:改造Cookie案例
-
Session:主菜
1. 概念:服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。HttpSession
2. 快速入门:
1. 获取HttpSession对象:
HttpSession session = request.getSession();
2. 使用HttpSession对象:
Object getAttribute(String name)
void setAttribute(String name, Object value)
void removeAttribute(String name)
3. 原理
* Session的实现是依赖于Cookie的。
4. 细节:
1. 当客户端关闭后,服务器不关闭,两次获取session是否为同一个?
* 默认情况下。不是。
* 如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。
Cookie c = new Cookie(“JSESSIONID”,session.getId());
c.setMaxAge(60*60);
response.addCookie©;
2. 客户端不关闭,服务器关闭后,两次获取的session是同一个吗?
* 不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
* session的钝化:
* 在服务器正常关闭之前,将session对象系列化到硬盘上
* session的活化:
* 在服务器启动后,将session文件转化为内存中的session对象即可。
3. session什么时候被销毁?
1. 服务器关闭
2. session对象调用invalidate() 。
3. session默认失效时间 30分钟
选择性配置修改
30
5. session的特点
1. session用于存储一次会话的多次请求的数据,存在服务器端
2. session可以存储任意类型,任意大小的数据
* session与Cookie的区别:
1. session存储数据在服务器端,Cookie在客户端
2. session没有数据大小限制,Cookie有
3. session数据安全,Cookie相对于不安全
案例:验证码
1. 案例需求:
1. 访问带有验证码的登录页面login.jsp
2. 用户输入用户名,密码以及验证码。
* 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
* 如果验证码输入有误,跳转登录页面,提示:验证码错误
* 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
2. 分析:
今日内容
1. JSP:
1. 指令
2. 注释
3. 内置对象
2. MVC开发模式
3. EL表达式
4. JSTL标签
5. 三层架构
JSP:
-
指令
- 作用:用于配置JSP页面,导入资源文件
- 格式:(好像<% %>是jsp代码的共有内容)
<%@ 指令名称 属性名1=属性值1 属性名2=属性值2 … %> - 分类:
-
page: 配置JSP页面的
- contentType:等同于response.setContentType()
- 设置响应体的mime类型以及字符集
- 设置当前jsp页面的编码(只能是高级的IDE才能生效,如果使用低级工具,则需要设置pageEncoding属性设置当前页面的字符集)
- import:导包,就是导入javase的那些包
- errorPage:当前页面发生异常(比如说500)后,会自动跳转到指定的错误页面
- isErrorPage:标识当前页面是否是错误页面。
- true:是,可以使用内置对象exception
- false:否。默认值。不可以使用内置对象exception
- contentType:等同于response.setContentType()
-
include :导入别的页面,比如导航之类的
-
<%@include file=“top.jsp”%>
3.taglib : 导入外部资源,比例导入标签库,下面例子就是导入jstl标签库
-
<%@ taglib prefix=“c” uri=“http://java.sun.com/jsp/jstl/core” %>
- prefix:前缀,自定义的,有点像xml文件引入外部约束一样在外部标签前面搞个前缀。
-
-
-
注释:
- html注释: :只能注释html代码片段
- jsp注释:推荐使用
<%-- --%>:可以注释所有
-
内置对象
- 在jsp页面中不需要创建,直接使用的对象
- 一共有9个:
变量名 真实类型 作用- pageContext PageContext 当前页面共享数据,还可以获取其他八个内置对象
- request HttpServletRequest 一次请求访问的多个资源(转发)
- session HttpSession 一次会话的多个请求间
- application ServletContext 所有用户间共享数据
- response HttpServletResponse 响应对象
- page Object 当前页面(Servlet)的对象 this
- out JspWriter 输出对象,数据输出到页面上
- config ServletConfig Servlet的配置对象
- exception Throwable 异常对象
MVC:开发模式
1. jsp演变历史
1. 早期只有servlet,只能使用response输出标签数据,非常麻烦
2. 后来又jsp,简化了Servlet的开发,如果过度使用jsp,在jsp中即写大量的java代码,有写html表,造成难于维护,难于分工协作
3. 再后来,java的web开发,借鉴mvc开发模式,使得程序的设计更加合理性
2. MVC:
1. M:Model,模型。JavaBean
* 完成具体的业务操作,如:查询数据库,封装对象
2. V:View,视图。JSP
* 展示数据
3. C:Controller,控制器。Servlet
* 获取用户的输入
* 调用模型
* 将数据交给视图进行展示
* 优缺点:
1. 优点:
1. 耦合性低,方便维护,可以利于分工协作
2. 重用性高
2. 缺点:
1. 使得项目架构变得复杂,对开发人员要求高
EL表达式
-
概念:Expression Language 表达式语言
-
作用:替换和简化jsp页面中java代码的编写
-
语法:${表达式}
-
注意:
- jsp默认支持el表达式的。如果要忽略el表达式
- 设置jsp中page指令中:isELIgnored=“true” 忽略当前jsp页面中所有的el表达式
- ${表达式} :忽略当前这个el表 达式
- jsp默认支持el表达式的。如果要忽略el表达式
-
使用:
-
运算:
- 运算符:
- 算数运算符: + - * /(div) %(mod)
- 比较运算符: > < >= <= == !=
- 逻辑运算符: &&(and) ||(or) !(not)
- 空运算符: empty
- 功能:用于判断字符串、集合、数组对象是否为null或者长度是否为0,有一个对都true,反之都不成立false
- ${empty list}:判断字符串、集合、数组对象是否为null或者长度为0
- ${not empty str}:表示判断字符串、集合、数组对象是否不为null 并且 长度>0
- 运算符:
-
获取值
- el表达式只能从域对象中获取值
- 语法:
-
${域名称.键名}:从指定域中获取指定键的值
- 域名称:
- pageScope --> pageContext
- requestScope --> request
- sessionScope --> session
- applicationScope --> application(ServletContext)
- 举例:在request域中存储了name=张三
- 获取:${requestScope.name}
- 域名称:
-
${键名}:表示依次从最小的域中查找是否有该键对应的值,直到找到为止。
-
获取对象、List集合、Map集合的值
1. 对象:${域名称.键名.属性名}
* 本质上会去调用对象的getter方法,并不是.操作符获得成员噢,只是老是成员名=属性名易产生误解!即操作属性=调用对应get/set方法=对相应的成员进行操作(看具体方法的实现,属性可不=成员名的)的get/set方法*当某个方法不对应一个成员变量,只是为了在页面上提供一个好看的格式,这种方法我们称为逻辑视图
public String getBirStr(){ if(birthday != null){ //1.格式化日期对象 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //2.返回字符串即可 return sdf.format(birthday); }else{ return ""; } }
2.List集合:**${域名称.键名[索引]}** 3.Map集合: **${域名称.键名.key名称}** **${域名称.键名["key名称"]}**
-
-
3.隐式对象:
el表达式中有11个隐式对象 pageContext:获取jsp其他八个内置对象 ${pageContext.request.contextPath}:动态获取虚拟目录(request是pageContext的属性,contextPath是request的属性,我们看api可见有getRequest方法在pageContext对象中)
-
JSTL
- 概念:JavaServer Pages Tag Library JSP标准标签库
- 是由Apache组织提供的开源的免费的jsp标签 <标签>
-
作用:用于简化和替换jsp页面上的java代码
-
使用步骤:
- 导入jstl相关jar包注意单单复制进去不行,还要在idea里面add as lib一下
- 引入标签库:taglib指令: <%@ taglib %>
- 使用标签
-
常用的JSTL标签
-
if:相当于java代码的if语句
-
属性:
- test 必须属性,接受boolean表达式
- 如果表达式为true,则显示if标签体内容,如果为false,则不显示标签体内容
- 一般情况下,**test属性值会结合el表达式(恰好可以返回true/flase)**一起使用
- test 必须属性,接受boolean表达式
-
注意:
* c:if标签**没有else情况**,想要else情况,则可以在定义一个c:if标签
-
-
choose:相当于java代码的switch语句
-
使用choose标签声明 相当于switch声明
- 使用when标签做判断 相当于case
-
使用otherwise标签做其他情况的声明 相当于default
-
-
foreach:相当于java代码的for语句
```java 1. 完成重复的操作 for(int i = 0; i < 10; i ++){ } * 属性: begin:开始值 end:结束值 var:临时变量 step:步长 varStatus:循环状态对象 index:容器中元素的索引,从0开始 count:循环次数,从1开始 <c:forEach begin="1" end="10" var="i" step="2" varStatus="s"> ${i} <h3>${s.index}<h3> <h4> ${s.count} </h4><br> </c:forEach> 2. 遍历容器 List<User> list; for(User user : list){ } * 属性: items:容器对象 var:容器中元素的临时变量 varStatus:循环状态对象 index:容器中元素的索引,从0开始 count:循环次数,从1开始 <% List list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); request.setAttribute("list",list); %> <c:forEach items="${list}" var="str" varStatus="s"> ${s.index} ${s.count} ${str}<br> </c:forEach>
-
练习:
- 需求:在request域中有一个存有User对象的List集合。需要使用jstl+el将list集合数据展示到jsp页面的表格table中
三层架构:软件设计架构
1. 界面层(表示层):用户看的得界面。用户可以通过界面上的组件和服务器进行交互
2. 业务逻辑层:处理业务逻辑的。
3. 数据访问层:操作数据存储文件。
1.我们之前都没写什么service层,为什么我们要加上这一层呢?看图可知是为了复用dao层的基本CRUD操作,对于这些基础操作组合起来的复杂的业务逻辑,我们在service层完成
2.既然都讲了service层,就顺带讲一下DAO层,全名 data access object。
通常我们会遇到很多要和数据库打交道的场景,如果为每一个场景都去写一些SQL语句,会让我们代码变得很奇怪,我们希望我们的代码比较干净整洁,那么一个很容易想到的方案就是把数据库封装一下,让我们和数据库的交道看起来比较像和一个对象打交道。这个对象通常就是DAO,当我们操作这个对象的时候,这个对象会自动的产生SQL语句来和数据库打交道,而我们只需要和DAO打交道就可以了。
简单讲,DAO就是把数据持久化包装成一个对象的访问(读写)
3.三层架构命名:web包:表示层,service包:业务逻辑层,dao包:数据访问层。方便看别人代码
案例:用户信息列表展示
1. 需求:用户信息的增删改查操作
2. 设计:
1. 技术选型:Servlet+JSP+MySQL+JDBCTempleat+Duird+BeanUtilS+tomcat
2. 数据库设计:
create database day17; -- 创建数据库
use day17; -- 使用数据库
create table user( -- 创建表
id int primary key auto_increment,
name varchar(20) not null,
gender varchar(5),
age int,
address varchar(32),
qq varchar(20),
email varchar(50)
);
3. 开发:
1. 环境搭建
1. 创建数据库环境
2. 创建项目,导入需要的jar包
2. 编码
4. 测试
5. 部署运维
分析:
重定向/转发适用场景:
1.由于重定向和原请求是两个独立的过程,所以重定向后就不能获得原页面的值或者是跳转之前要对请求进行的一些预处理了(session在多次请求间共享数据共享数据),而转发就没有这个问题,因为request和response都顺带带过去了。这里适用转发没啥问题
2.个人认为这个过程直觉上也应该被认为是一次请求,恰符合转发特点,而重定向不同。
ps:附上idea上连接数据库,好像有这个只要创建好了表和数据,关了数据库管理工具也没事!
同时注意配置文件和jdbc工具类可以每次复用,其中配置文件要改一下database名噢!
今日内容
1. 综合练习
1. 简单功能
1. 列表查询
2. 登录
3. 添加
4. 删除
5. 修改
2. 复杂功能
1. 删除选中
2. 分页查询
* 好处:
1. 减轻服务器内存的开销
2. 提升用户体验
3. 复杂条件查询
2. 登录
1. 调整页面,加入验证码功能
2. 代码实现
3.添加
4.删除
5.修改
6.删除选中
7.分页查询
这种复杂功能该怎么构造思路?
!从输入和输出入手!,输出即想一下服务器要给客户端输出什么东西才能让客户端展现出需求所要求的样子,这里就是想DAO有那些成员变量。输入即客户端要给服务器什么参数才能让服务器能输出东西给客户端展示,再想一下这些参数怎么传递.
8.复杂条件分页查询
1.复杂条件查询也用分页查询的servlet,
想一想,不就是多几个参数(条件)的问题嘛,
其他差不多,so后续其实是在原来分页查询的方法为基础改一下即可,即不带条件的查询只是正常查询的一种特殊情况,不当成两个情况增加代码复用性。
2.StringBuilder类
可看作可变的字符串。
还记得正常String的实体是不可改变的嘛,方法都是生成了新的字符串,这个缓冲类则是可以改变的。
常用方法:append,toString,构造方法传入string
3.有没有值时我们总是判断两种情况,
因为不同方法返回各不相同有的是null,有的是空字符串
就像我们判断list一样总是判断 null或者长度为0一样,懒得去看方法返回值了
currentPage == null || “”.equals(currentPage)
今日内容
1. Filter:过滤器
2. Listener:监听器
web三大组件:servlet Filter Listener
Filter:过滤器
-
概念:
-
生活中的过滤器:净水器,空气净化器,土匪、
-
web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。与资源息息相关
-
过滤器的作用:
-
一般用于完成通用的操作(即对于一些资源都要做的操作)。如:登录验证、统一编码处理、敏感字符过滤…
- 增强消息数据
-
-
快速入门:
-
步骤:
- 定义一个类,实现接口Filter
- 复写方法
- 配置拦截路径(过滤器生效的资源路径)
- web.xml
- 注解
-
代码:
@WebFilter("/*")//访问所有资源之前,都会执行该过滤器
public class FilterDemo1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("filterDemo1被执行了....");
-
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
-
过滤器细节:
-
web.xml配置 (和servlet很类似)
demo1
cn.itcast.web.filter.FilterDemo1
demo1
/*
-
过滤器执行流程(即过滤器对于请求和响应都可以做数据增强)
- 执行过滤器
- 执行放行后的资源
- 回来执行过滤器放行代码下边的代码
-
过滤器生命周期方法
- init:在服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源
- doFilter:每一次请求被拦截资源时,会执行。执行多次
- destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源
-
过滤器配置详解
- 拦截路径配置(可见不用带虚拟目录):
- 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
- 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行。项目自带那个web目录下资源的路径在项目虚拟路径下,如果有文件夹,则路径多加一个文件夹名
- 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行
- 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
- 拦截方式配置:资源被访问的方式
- 在注解处配置:
- 设置dispatcherTypes属性(和配置value属性一样的格式,注意这个属性可配置一个数组)
- REQUEST:默认值。浏览器(客户端)直接请求资源
- FORWARD:转发访问资源
- INCLUDE:包含访问资源
- ERROR:错误跳转资源
- ASYNC:异步访问资源
- 设置dispatcherTypes属性(和配置value属性一样的格式,注意这个属性可配置一个数组)
- 在web.xml里面配置
- 设置标签即可
- 在注解处配置:
- 拦截路径配置(可见不用带虚拟目录):
-
过滤器链(配置多个过滤器)
-
执行顺序(看土匪原理图就知道了):如果有两个过滤器:过滤器1和过滤器2
- 过滤器1
- 过滤器2
- 资源执行
- 过滤器2
- 过滤器1
-
过滤器先后顺序问题(即为啥不是先过滤器2再过滤器1的问题?):
- 注解配置:按照类名的字符串比较规则比较,值小的先执行
- 如: AFilter 和 BFilter,AFilter就先执行了。
- web.xml配置: 谁定义在上边,谁先执行
- 注解配置:按照类名的字符串比较规则比较,值小的先执行
-
-
-
案例:
- 案例1_登录验证
- 需求:
- 访问day17_case案例的资源。验证其是否登录
- 如果登录了,则直接放行。
- 如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"。
- 需求:
- 案例1_登录验证
-
案例2_敏感词汇过滤
-
需求:
- 对day17_case案例录入的数据进行敏感词汇过滤
- 敏感词汇参考《敏感词汇.txt》
- 如果是敏感词汇,替换为 ***
-
分析:
- 对request对象进行增强。增强获取参数相关方法(本质是request没有setParameter方法,不能去修改参数,只能对其进行获得参数的各种方法增强,使其获得的是处理后的,代码里面只演示了对其中一个方法的增强)
- 放行。传递代理对象
怎么增强对象的功能?:
设计模式:一些通用的解决固定问题的方式,下面两种设计模式都能增强对象的功能- 装饰模式
- 代理模式
概念:
1. 真实对象:被代理的对象
2. 代理对象:
3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
实现方式:
1. 静态代理:有一个类文件描述代理模式
2. 动态代理:在内存中形成代理类
实现步骤:
1. 代理对象和真实对象实现相同的接口
2. 接口类 代理对象 = (接口)Proxy.newProxyInstance(固定的三个参数);
3. 使用代理对象调用方法。
4. 增强方法
1. 增强参数列表
2. 增强返回值类型
3. 增强方法体执行逻辑
-
Listener:监听器
* 概念:web的三大组件之一。
* 事件监听机制
* 事件 :一件事情
* 事件源 :事件发生的地方
* 监听器 :一个对象
* 注册监听:将事件、事件源、监听器绑定在一起。 当事件源上发生某个事件后,执行监听器代码
-
ServletContextListener:监听ServletContext对象的创建和销毁,回忆一下之前讲过ServletContext对象,其是一个范围最大的域对象,范围为整个服务器,生命周期自然也=服务器的生命周期,且tomcat自动创建。
作用之一:获取供整个服务器使用的资源
- 方法:
- void contextDestroyed(ServletContextEvent sce) :ServletContext对象被销毁之前会调用该方法
- void contextInitialized(ServletContextEvent sce) :ServletContext对象创建后会调用该方法
- 步骤:
- 定义一个类,实现ServletContextListener接口
- 复写方法
- 配置
- web.xml
指定监听器类的全类名如下
cn.itcast.web.listener.ContextLoaderListener</listener> 指定servletContext的初始化参数如下 <context-param> 2.注解: @WebListener即可标注这是一个监听器类
- 方法: