JavaWeb
JavaWeb概述
Web服务器
Web服务器的作用是接收客户端的请求,给客户端作出响应,一个连接了网络的应用程序,一定会有一个服务器
做一个面向互联网的应用程序,你就需要一个24小时开启的服务器,以便于你的客户可以随时使用程序
Java的Web开发是基于B/S架构的,所以将在浏览器上编写程序
Tomcat
Tomcat是一个免费的开放源代码的Web应用服务器,我们的项目程序都会运行在服务器之上
安装与配置
下载压缩包,解压
使用Tomcat之前,要现在系统环境变量中添加JAVA_HOME变量,值为JDK目录
再在Path变量下添加%JAVA_HOME%/bin,之后就可以正常运行
进入bin目录,启动startup.bat以启动Tomcat,shutdown.bat关闭Tomcat
使用Tomcat时,不要关闭启动的cmd界面,startup.bat相当于启动服务器,你把这个关了就等于把你服务器关了
Http
超文本传输协议,是一个简单的请求-响应协议,运行于TCP之上
Http报文
Http通过发送Http报文,完成请求与响应
Http报文分为请求报文和响应报文两种
1.请求报文
请求报文由请求行,首部和主题实体组成
- 请求行:包括请求类型(get,post),URL和Http版本
- 首部:用于浏览器和web服务器之间信息交换的附加信息
2.响应报文
响应报文由状态行,首部和实体主体组成
- 状态行:包括Http版本,状态码(404(找不到资源),200(表示成功))和状态短语
- 首部:和请求报文基本相似
响应码
-
1xx(信息性状态码):表示接收的请求正在处理。
-
2xx(成功状态码):表示请求正常处理完毕。
200:表示请求成功。一般用于GET与POST请求
-
3xx(重定向状态码):需要后续操作才能完成这一请求。
-
4xx(客户端错误状态码):表示请求包含语法错误或无法完成。
404:找不到资源,一般是路径写错了
- 5xx(服务器错误状态码):服务器在处理请求的过程中发生了错误。
500:服务器内部错误,你代码写错了
Http标头
HTTP 标头 - HTTP | MDN (mozilla.org)
标头(Header)控制着报文的具体信息
Maven
Maven是一种架构管理工具,用来帮助我们管理项目 是什么Maven?
我们在实际开发时,必然会用到外部库,而且外部库的数量不小,如果没有合适的管理,使用起来就会异常杂乱
Maven就像一个仓库管理员,它会去管理你导入的外部库。在这里将外部库称之为依赖。
Maven会提供以下等功能
- 中央仓库:Maven有一个云端的中央仓库,包含着大量公开的依赖;并且会在本地部署一个本地仓库,用于存放依赖。当你声明要使用一个依赖时,会先从中央仓库查找是否有这个依赖,如果没有,去中央仓库下载
- 依赖传递:如果你的依赖A必须要有另一个依赖B才能运行,那么Maven会当你使用依赖A时自动导入依赖B
- 版本管理:你可以为每个依赖制定一个版本号,以确保项目使用了正确稳定的库版本
- 范围:你可以为依赖制定范围,来确定依赖在何时可用,或者是否应该包含在最终的构建结果中
Maven的核心思想:约定大于配置。Maven会规定好该如何去设计项目,必须按照一定的规范进行
Maven父子工程
如同类的继承关系一样,Maven为不同的Project赋予的继承的属性,子工程可以使用父工程的所有jar包,父工程可以通过范围来决定依赖是否提供给子工程
在父工程的pom.xml文件中,moudle标签中会存放子项目列表
<modules>
<module>web1</module>
<module>web2</module>
</modules>
Maven环境配置
使用Maven前,先设置MAVEN_HOME和M2_HOME两个环境变量,一个是Maven的文件目录一个是bin目录,再在Path里添加上%MAVEN_HOME%\bin
然后找到conf的settting.xml文件,将mirror修改为国内的阿里云maven镜像,百度就能找到
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
本地仓库
在settting.xml文件中,找到localRepository,可以修改本地仓库位置
<localRepository>本地仓库路径</localRepository>
IDEA中设置
file->settings->build,execution,deployment->build tools->maven
将用户设置文件和本地仓库的路径设置为自己的Maven所在路径
再回到IDEA主页面,点击所有设置,同样的修改以上设置。这一步是全局配置
Junit
Junit是每个Maven项目创建时自动添加的依赖
<!--junit-->
<!--该依赖提供单元测试,通常只有main方法才能运行,通过junit,在要运行的方法添加注解@Test,即可运行该方法-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
Maven导致的配置文件失效问题
由于Maven的约定大于配置,会导致配置文件(如.xml文件)无法导出或无法生效的问题。解决方案如下:
在pom.xml文件内,添加以下build标签,将资源暴露出来
<build>
<resources>
<!--使directory路径下的include类型文件能够被使用-->
<resource>
<directory>src.main.resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src.main.java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
创建Web项目
Tomcat提供了Web服务器,Maven是架构管理工具,通过这二者我们就能部署一个基本的Web程序
创建带有Maven的Web项目
创建完Maven项目后,就会自动下载Maven相关文件
下载完成后,删除pom.xml中15行的< scope>test< /scope>,再重构一次Maven项目
部署Tomcat
设置运行/调试配置
Servlet
目前的B/S架构应用都是用Http协议进行数据传输,Http协议通过请求/应答来完成数据的传输
Servlet是Tomcat提供的一个接口,用来处理请求的业务逻辑。
导入Servlet资源
要使用Servlet,就要先导入Servlet包;导入外部资源,就要用到Maven
在pom.xml文件中,添加以下依赖
<!--Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
添加完后,重构Maven即可
Servlet的运行过程
Servlet接口提供了几个方法,源码如下
package javax.servlet;
import java.io.IOException;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
//init,service,destroy是生命周期方法
//init和destroy仅执行一次,即Servlet的创建和销毁
//service在每次有新请求时被调用。主要的业务逻辑代码就写在这里
由于Tomcat将底层的TCP连接和Http请求/响应过程都已经完成,我们无需关注底层连接,只需要编写业务逻辑代码即可,集合到Servlet接口中,展现给我们的只有三个对象:ServletConfig,ServletRequest,ServletResponse。
- ServletConfig
ServletConfig对象封装了servlet的一些参数信息。如果需要,我们可以从它获取。
- Request/Response
当Http请求到了Tomcat服务器后,Tomcat通过字符串解析,将Header,URL,Method等信息封装进Request对象中,等待Servlet调用
对于Response,Tomcat将其传入Servlet时是一个空对象,通过Servlet一系列处理后才会有信息,Tomcat再将里面的信息组装成Http响应,发送给客户端
浏览器发送请求最基本的方式有两种:Get和Post,那么不同的请求方法就一定要有不同的处理方式
通过实现接口中的service方法,来区分不同的请求方式
Servlet在Java中有两个具体的实现类:GenericServlet 和 HttpServlet。其中HttpServlet类实现了service方法
//HttpServlet类中的service方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {//------------------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")) {//------------------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);
}
}
每当有请求时service方法都会被调用,根据请求的Method属性,找到对应的请求方法所要执行的业务逻辑代码
如Get方法去执行doGet,Post方法执行doPost
但是HttpServlet中的这些do方法,并不是直接使用的,到了这里就是实际的业务逻辑部分了,如果我们不去重写do方法,返回给我们的只有错误码。你不去设计具体怎么做,默认就是报错
//HttpServlet类中的doGet方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);//无论怎样,返回的都是错误码
}
}
现在我们就要实现一个继承了HttpServlet的类,来重写do方法
public class MyServlet extends HttpServlet {
//重写doGet,每当有一个Get类型的请求时,最终会转到执行这个方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("DoGet");
PrintWriter writer=resp.getWriter();
writer.println("thsiisget");
}
//重写doPost,每当有一个Post类型的请求时,最终会转到执行这个方法
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("DoPost");
PrintWriter writer=resp.getWriter();
writer.println("thsiispost");
}
}
总之,service方法只是判断并转发请求,具体的业务逻辑在do方法里实现
将Servlet类部署到Web服务器中
打开Web.xml文件,添加
<!--注册Servlet-->
<servlet>
<!--请求名-->
<servlet-name>hello</servlet-name>
<!--执行请求的Servlet类-->
<servlet-class>MyServlet</servlet-class>
</servlet>
<!--Servlet的请求路径-->
<servlet-mapping>
<!--与上面的请求名一致-->
<servlet-name>hello</servlet-name>
<!--映射路径-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
后记:这样注册Servlet类,会造成JSP页面显示源码,注释掉Servlet和Servlet-Mapping又会恢复正常。
注册Servlet类还可以通过注解形式
首先要将Web.xml文件开头的metadata-complete改为"false"
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="false">
<!-- 如果该值为true,表示整个项目部署时配置已经完全,则只会使用web.xml文件里的配置-->
<!--为false,则会扫描所有文件的注解,并且加载配置-->
</web-app>
然后再Servlet类前添加注解,以注册Servlet
//name相当于<servlet-name>,,urlPatterns=<url-pattern>,loadOnstartup为加载优先级,越小越高
//经过测试,如果name=jsp的话,会显示源码,url=jsp的话,第一次显示源码,在请求一次就正常了
//所以不要将值写成关键字
@WebServlet(name="jsp1",urlPatterns = "/jsp1",loadOnStartup=1)
public class JSPTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
通过注解注册Servlet不会出现问题
Web开发 web.xml中metadata-complete属性
请求映射
一个具体的http请求,实际操作就是在网址后加上其他路径,不同的路径请求的资源也不相同
此时就需要一个请求映射,不同的请求映射到不同的操作上
在上面我们部署Servlet类时,在servlet标签内定义了请求名和执行请求的类,还在servlet-mapping中设置了它的映射。
当我们访问了url-pattern的路径后,就会跳转到对应请求名的类上,根据请求的Method类型调用do方法
映射遵循以下规则:
1.一个Servlet类可以指定一个映射路径
2.一个Servlet类可以指定多个映射路径
3.一个Servlet类可以指定通用映射路径
即映射可以写成这样:
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
<!-- 一个Servlet类提供给多个映射使用-->
<servlet-name>hello</servlet-name>
<!-- 指定通用映射路径,*为通配符,即请求/123+任意字符,都会跳转到hello-->
<url-pattern>/123/*</url-pattern>
</servlet-mapping>
ServletContext对象
一个Web容器启动时,会为其中的每个Web项目创建一个通用的ServletContext对象,用于代表这些项目
Context对象能提供以下功能
1.共享数据
一个Servlet类中的数据,可以共享给其他Servlet类使用
比如我定义了两个Servlet类,Servlet1和Servlet2
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String username="zhang";
//向Context中存放了一个key=username,value=username字符串的值的信息
context.setAttribute("username",username);
PrintWriter writer=resp.getWriter();
writer.println("thsiispost");
}
}
public class Servlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//从Context中提取key=username的值
String string=(String)context.getAttribute("username");
resp.setContentType("text/html");
resp.setCharacterEncoding("GBK");//编码格式
resp.getWriter().print("名字="+string);
System.out.println(string);
}
}
在Servlet1类中,我们使用setAttribute方法向Context对象中存放了一个值,在Servlet2类中,我们可以提取出来这个值
设置好映射
<servlet>
<servlet-name>GetContext</servlet-name>
<servlet-class>Servlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GetContext</servlet-name>
<url-pattern>/GC</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>Servlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
先去请求Servlet1类,将值存入Context对象中,然后请求Servlet2类,就可以得到这个值
2.获取初始化参数
在web.xml里,可以设置一些初始化参数
<context-param>
<!--key=url,value=value-->
<param-name>url</param-name>
<param-value>value</param-value>
</context-param>
这些参数也可以通过Context对象获取到
public class Servlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//获取key=url的初始化参数
context.getInitParameter("url");
}
}
3.请求转发
有时候一个请求需要多个Servlet类执行,那么在Servlet1类执行完后,就要把这个请求转发到Servlet2类中
public class Servlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
context.getInitParameter("url");
RequestDispatcher rd = context.getRequestDispatcher("/s2");//设置转发路径,RequestDispatcher请求调度类
rd.forward(req, resp);//将Request和Response对象转发到路径所在位置
}
}
当请求/s3时,执行到forward后就会跳转到/s2的页面,请求也会转发到这里
请求转发有两种方式
getServletContext().getRequestDispatcher("/s2").forward(req,resp);
req.getRequestDispatcher("/s2").forward(req,resp);
4.读取资源文件
public class Servlet5 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//读取properties文件信息
InputStream fis=this.getServletContext().getResourceAsStream("WEB-INF/classes/db.properties");
Properties properties=new Properties();
properties.load(fis);
String username=properties.getProperty("username");
String password=properties.getProperty("password");
resp.getWriter().println(username);
resp.getWriter().println(password);
}
}
#properties文件
username=root
password=123456
Response对象
Web服务器接收到客户端的http请求时,Servlet就会创建一个代表请求的HttpServletRequest对象和一个代表响应的HttpServletResponse对象
- HttpServletRequest包含着客户端请求过来的参数
- HttpsResponse包含响应给客户端的信息
源码分析
源码信息可以大致分为以下几类
向浏览器输出的方法
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
向浏览器发送响应头的方法
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
void setDateHeader(String var1, long var2);
void addDateHeader(String var1, long var2);
void setHeader(String var1, String var2);
void addHeader(String var1, String var2);
void setIntHeader(String var1, int var2);
void addIntHeader(String var1, int var2);
响应的状态码
int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;
int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;
int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;
int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;
int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
1.向浏览器输出消息
resp.getWriter().println("");
2.下载文件
下载文件的过程如下
- 获取下载文件的路径,确定下载的文件名
- 设置content-disposition响应头,控制浏览器以下载的形式打开文件
- 获取下载文件输入流
- 创建缓冲区
- 获取OutputStream对象
- 将FileOutputStream流写入到Buffer缓冲区
- 使用OutputStream对象将缓冲区中数据输出到客户端
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//获取下载文件的路径,确定下载的文件名
String realpath="E:\\WorkSpaces\\IdeaProjects\\MavenWeb\\ResponseTest\\src\\main\\resources\\1.jpg";
String filename=realpath.substring(realpath.lastIndexOf("\\")+1);
//设置content-disposition响应头,控制浏览器以下载的形式打开文件
resp.setHeader("Content-disposition","attachment;filename"+ filename);
//到这一步,文件将以字节流的形式下载下来(一个txt文件)。我们要通过IO流将其转化为具体的媒体格式
//获取下载文件输入流,输出流对象
FileInputStream fis=new FileInputStream(realpath);
ServletOutputStream out=resp.getOutputStream();
//创建缓冲区
int len=0;
byte[] buffer=new byte[1024];
//输入流将文件信息读入缓冲区,输出流将缓冲区内信息写出
//从Response对象获取的输出流,输出对象是客户端,即是输出到客户端下载的文件
//经典的文件复制过程,看不懂回去看IO流
while((len=fis.read(buffer))>0){
out.write(buffer,0,len);
}
//关闭流
fis.close();
out.close();
}
}
3.验证码
验证码实际上就是长度固定的随机数
4.实现重定向
一个Web资源收到客户端请求后,会通知客户端去访问另外一个Web资源,这个过程叫重定向
例如用户登录后,从登录页面跳转到其他页面
resp.sendRedirect("/file");//重定向到/file
重定向实际上是进行了以下两步
//Location首部指定的是需要将页面重新定向至的地址
//一般在响应码为 3xx 的响应中才会有意义
resp.setHeader("Location","/file");
//将响应码设置为302,使页面跳转
resp.setStatus(302);
重定向与转发的区别:
相同:
- 页面都会实现跳转
不同
- 转发是服务器行为,服务器内部资源只能将请求转发给其他内部资源,实现跳转
- 重定向是客户端行为,客户端请求一个新的url,可以跳转到外部资源
后记:
当你重定向到jsp后出现404时,看这个
//sendRedirect方法必须添加虚拟路径:
resp.sendRedirect(req.getContextPath()+"/success.jsp");
Request对象
获取参数
Request对象可以获取表单内信息
<html>
<body>
<h2>Hello World!</h2>
<div style="align-items: center">
<%--action:表单提交到action内的请求--%>
<form action="${pageContext.request.contextPath}/login" method="post">
<%--Request对象将根据name找值--%>
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="checkbox" name="c1" value="apple">apple<br>
<input type="checkbox" name="c1" value="banana">banana<br>
<input type="checkbox" name="c1" value="orange">orange<br>
<input type="submit" value="submit">
</form>
</div>
</body>
</html>
public class RequestText extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// req.getParameter("")获取参数方法,传入一个String,找到表单内对应name的值,以String返回
String username = req.getParameter("username");
String password = req.getParameter("password");
//对于checkbox,由于有多个值,要用getParameterValues(方法),返回一个String数组
String[] checkbox = req.getParameterValues("c1");
System.out.println(username);
System.out.println(password);
System.out.println(Arrays.toString(checkbox));
resp.sendRedirect(req.getContextPath()+"/success.jsp");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
请求转发
Request对象也可以进行请求转发,详情见ServletContext的请求转发
Cookie,Session
会话,Cookie,Session
会话:会话是浏览器和服务器之间的多次请求和响应
从浏览器访问服务器开始,到访问服务器结束,浏览器关闭为止的这段时间内容产生的多次请求和响应,合起来叫做浏览器和服务器之间的一次会话
Http是一种无状态的协议
// HTTP无状态协议,是指协议对于交互性场景没有记忆能力
// 你在一次请求中所产生的所有数据不会被服务器记录下来,在下一次请求就会被彻底忘记
// 举例来说,你在一个网站完成了登录,但当你刷新了页面,服务器就忘记了你的登录信息,需要你重新登录
// 这种情况是不能接受的,例如你在淘宝买东西,进淘宝登录,选商品登录,买商品还要登录。
// 为了解决这个问题,Cookie和Session应运而生
Cookie和Session是两种用于保持HTTP连接状态的技术:
-
Cookie相当于一个通行证,当客户端请求服务器时,服务器就会通过Response对象返回给客户端一个Cookie,之后每次请求时客户端会将Cookie放在Http的Header里,服务器检查该Cookie,以此辨别用户状态
-
Cookie是存放在客户端的,一般当你在网站登录时选择记住我,服务器就会发给你一个Cookie,只要你不删除它,就不用再次登录了
-
Cookie具有跨域名性,不同网站的Cookie不会混淆。例如百度的Cookie不会在请求其他网站时使用
-
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上
-
服务器端为每个用户创建了唯一的session,当服务器接受到请求时,从Request对象中获取到Cookie信息,再去查找Session
Cookie
Cookie有三个主要信息:name,value个Maxage
Cookie cookie=new Cookie("name","value");//name和value可以通过get和set方法设置。不建议修改name
cookie.setMaxAge(1);//设置cookie的最大生命周期,超过此时间cookie会被删除。1表示1秒
Cookie[] cookies = req.getCookies();//可以获取到Request对象中的Cookie(可能有多个)
- 一个Cookie只能保存一个信息(value)
- 一个Web站点可以给客户端浏览器发送多个Cookie,最多存放20个
- Cookie大小限制4kb
- 浏览器最多存放300个Cookie
Session
一个浏览器访问Web网站时,就会产生一个Session,只要浏览器不关闭(会话不结束),Session就一直存在
不同的浏览器会创建不同的Session(相当于不同的客户端)
现在将Session理解为存储Cookie即可
当一个Session被创建时,执行了以下步骤:
HttpSession session=req.getSession();
Cookie cookie = new Cookie("name",session.getID());
resp.addCookie(cookie);//将Cookie添加到Response对象中
//addCookie后,就可以通过req.GetCookie方法获得
Session可以添加参数
session.setAttribute("name","value");
session.getAttribute("name");
session.removeAttribute("name");
可以注销Session,但会立刻产生一个新的Session
session.invalidate();
//如果没有Session去请求服务器,会产生500
可以在Web.xml文件里修改Session的持续时间
<session-config>
<!--以分钟为单位-->
<session-timeout>1</session-timeout>
</session-config>
每一个Session都有一个唯一的ID,用于标识Session
session.getID();
当客户端访问服务器时,服务器执行以下步骤:
- 创建一个唯一的Session
- 返回给客户端一个Cookie,值为Session的id
JSP
浅显来说,JSP就是写在HTML文件里的Java代码
但是JSP用起来时不适合写Java代码,现在更多的是前后端分离,JSP更多用于输出一些东西
JSP(JavaServerPage),java服务器端页面,也是用来开发动态Web的技术
动态Web的定义是可以自己产生页面的。例如一个视频网站,你不可能用户上传一个视频,你去专门做一个该视频的页面。动态Web页面会自动生成
现在我们接触到的向页面写入的方法有
resp.getWriter().println("");
如果用printWriter来写页面,就会变成这样
PrintWriter writer = resp.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Session Test</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("<h1>Session Test</h1>");
writer.println("</body>");
writer.println("</html>");
手动来写这些显然是不可接受的,JSP应运而生
JSP和HTML的区别:
- HTML只提供静态的数据
- JSP页面中可以嵌入Java代码,提供动态数据
HttpJspPage jspPage;//一个JSP对象
HttpJspPage类继承了JspPage,JspPage继承了Servlet类,所以本质上JSP还是一个Servlet类
JSP在运行过程中会被转化为一个java类。从源码来看,JSP也是通过PrintWriter的print方法来写网页的
//初始化
public void _jspInit() {
}
//销毁
public void _jspDestroy() {
}
//Service方法,判断请求
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
if (!javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
final java.lang.String _jspx_method = request.getMethod();
if ("OPTIONS".equals(_jspx_method)) {
response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
return;
}
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) {
response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS");
return;
}
}
JSP内置了一些对象
final javax.servlet.jsp.PageContext pageContext;//页面上下文
final javax.servlet.ServletContext application;//applicationContext ,实际上是ServletContext对象
final javax.servlet.ServletConfig config;//config,ServletConfig对象
javax.servlet.jsp.JspWriter out = null;//out,JspWriter
final java.lang.Object page = this;//page:当前页面
Jsp的工作流程
请求JSP页面→代码部分转换为java类→编译执行代码→代码执行后数据嵌入到页面中→最终页面响应给客户端
JSP基础语法
使用JSP前,先导入以下依赖
<!--JSP-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
JSP支持Java中的所有语法
注释形式:<%--注释体--%>
JSP表达式
<%-- <% %>这种格式是写java代码的 --%>
<%
int sum=0;
for (int i = 0; i < =100; i++){
sum+=i;
}
%>
<%-- <%= 变量%>这种格式会向网页输出变量的值 --%>
<%=sum%>
<%-- 一样的效果,这称为EL表达式 --%>
${sum}
JSP代码里可以嵌入HTML代码,HTML代码可以嵌入JSP代码
<%
for (int i = 0; i <10; i++) {
%>
<%--这个p标签会被循环写出10次--%>
<p>HELLOWORLD<%=i%></p>
<%
}
%>
效果图:
可以声明方法
<%-- <%! %>这个格式里面声明方法 --%>
<%!
public void menthod(){
System.out.println(1);
}
%>
<%
menthod();
%>
<% %> <%--java代码--%>
<%= %> <%--输出变量值--%>
<%! %> <%--声明方法--%>
<%--注释--%>
JSP指令
JSP指令通常是用于修改一些设置
<%@ %>这个格式写指令
例如JSP文件生成时自带的指令
<%--指令类型page 上下文类型:html文件,UTF-8编码 所用语言:java --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="" %> <%--导包--%>
<%@ page errorPage="/页面路径"%> <%--确定错误页面,出现错误时转到此页面--%>
<%@ page isErrorPage="true" %> <%--是否是一个错误页面--%>
<%@ page pageEncoding="UTF-8" %> <%--页面编码--%>
<%@ include file="index.jsp"%> <%--如同C语言的include一样,将其他文件放到include位置,合二为一--%>
JSP内置对象
介绍这些对象的方法
- pageContext
可以找到其他对象的Attribute
<body>
<%
pageContext.setAttribute("name1","value1");
session.setAttribute("name2","value2");
request.setAttribute("name3","value3");
application.setAttribute("name4","value4");
%>
<%
String s1=(String)pageContext.findAttribute("name1");
String s2=(String)pageContext.findAttribute("name2");
String s3=(String)pageContext.findAttribute("name3");
String s4=(String)pageContext.findAttribute("name4");
%>
name1=<%=s1%><br>
name2=<%=s2%><br>
name3=<%=s3%><br>
name4=<%=s4%><br>
</body>
不同对象的Attribute有不同的生命周期:
pageContext:保存的数据只在一个页面中有效,切换页面数据就消失
request:数据只在一次请求中有效,转发会携带这个数据,重新请求消失
session:数据只在一次会话中有效,会话结束数据消失
application:数据保存在服务器中,服务器关闭才失效
pageContext的findAttribute()方法,采用从低向上的方法查找Attribute,即顺序为page→Request→Session→applicantion
-
request
-
response
-
session
-
applicantion(ServletContent)
这几个对象用来承载产生的数据:
request:客户端向服务器发送请求,产生的数据。使用一次就没用了,例如新闻文档
session:客户端向服务器发送请求,产生的数据。一个用户使用多次,例如购物车
applicantion:客户端向服务器发送请求,产生的数据。一个用户用完其他用户还可能使用。例如聊天数据
-
config(ServletConfig)
-
out:数据
-
page:几乎不再使用
-
exception:异常
EL表达式,JSP标签,JSTL标签
EL表达式
${ }
可以放在字符串中,最终代表一个值
功能:
- 获取数据
<% String s1="string1"%>
${s1}
- 执行运算
- 获取Web开发常用对象
JSP标签
<%--执行转发,可以携带数据;相当于请求转发到localhost:8080/500.jsp?name1=value1&name2=value2--%>
<jsp:forward page="500.jsp">
<jsp:param name="name1" value="value1"></jsp:param>
<jsp:param name="name2" value="value2"></jsp:param>
</jsp:forward>
<%--从Request对象里可以取出来--%>
<%=request.getParameter("name1")%>
<%=request.getParameter("name2")%>
<%--setAttribute的数据用getAttribute--%>
<%--param数据用getParameter--%>
JSTL标签
JSTL即为JSP 标准标签库,封装了JSP应用的通用核心功能
JSTL的使用是为了弥补HTML标签的不足,包括一下标签:
核心标签,格式化标签,SQL标签,XML标签,JSTL 函数
目前只了解部分核心标签即可
使用前先引入依赖:
<!--JSTL-->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--JSTL-api-->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!--JSTL-impl-->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jstl-impl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
<!--jsp标准库-->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
使用核心标签前,在需要使用的jsp文件头,先引入核心标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
如果使用中出现500错误,java.lang.NoClassDefFoundError错误。试着把这些依赖的jar包放到Tomcat的lib目录下
<c:out value="123"/><%--相当于out.print,向网页输出值--%>
<%--if判断,text内为判断条件(用EL表达式),var定义一个变量,值为判断条件的结果T/F,scope作用于,page,session等--%>
<%--若为true,执行标签内语句,var变量值为true--%>
<c:if test="${2>1}" var="isAdmin" scope="page">
<c:out value="Yes"/>
</c:if>
<%--定义一个变量,名为number1值=100--%>
<c:set var="number1" value="100"/>
<%--选择标签,choose相当于switch,when相当于case--%>
<c:choose>
<c:when test="${}">
<c:out value=""/>
</c:when>
<c:when test="${}">
<c:out value=""/>
</c:when>
</c:choose>
JavaBean
JavaBean是一个自定义的实体类,要满足以下要求:
- 必须有一个无参构造
- 属性必须私有化
- 每个属性有对应的Get/Set方法
一般用来和数据库的字段做映射
MVC三层架构
MVC:Model,View,Controller,将程序架构分为模型,显示,控制器三部分
Controller:接收用户请求,交给业务层执行请求,执行重定向或转发,例如Servlet
View:向用户展示数据模型,提供可以供用户操作的请求,例如JSP
Model:控制业务操作,增删改查数据(数据持久化),例如service方法,Dao层
Servlet和JSP都能写Java代码,为了利于维护,设置了以下规定:
- Servlet专注于处理请求,以及控制视图跳转
- JSP专注于处理数据
Filter
Filter,过滤器
过滤器过滤的是请求和数据,例如垃圾请求和乱码
乱码可以通过在类中设置编码解决,但是如果有成千上万个类就不能去一个个设置了
通过继承Filter接口实现过滤器类
//使用javax.servlet包下的Filter
public class MyFliter implements Filter {
//init和destroy,生命周期方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
//过滤
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
}
进入过滤器的请求会执行doFilter方法
doFilter有三个参数
- servletRequest,servletResponse分别代表Request和Response对象,该有的方法都有
- filterChain用于执行转发,让请求继续传递
例如我们想过滤掉所有乱码,就在doFilter方法里设置Request和Response对象的编码
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
//该方法让请求继续向下传递,否则请求会被拦截在这里
filterChain.doFilter(servletRequest, servletResponse);
}
然后再Web.xml文件里配置Filter
<filter>
<filter-name>filter</filter-name>
<filter-class>MyFliter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/s1</url-pattern><!--s1请求将经过该过滤器-->
</filter-mapping>
过滤器还可以过滤请求,不让用户访问某些页面
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("GBK");
servletResponse.setCharacterEncoding("GBK");
String username = servletRequest.getParameter("username");
String password = servletRequest.getParameter("password");
if(username.equals("admin")) {
//如果是admin,可以执行请求
filterChain.doFilter(servletRequest, servletResponse);
}else{
//不是admin,请求被拦截在这里,不再执行
PrintWriter out = servletResponse.getWriter();
out.println("You are not allowed to access this resource");
}
}
Listener
Listener监听器,监听器有许多个接口,不同接口的实现类监听不同的事件
例如Session监听器
class listener implements HttpSessionListener {
//Session创建和销毁时执行方法
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSessionListener.super.sessionDestroyed(se);
}
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSessionListener.super.sessionCreated(se);
}
}
监听器需要再web.xml文件里注册
<listener>
<listener-class>listener</listener-class>
</listener>
JDBC
Java程序连接数据库时需要手动添加mysql驱动。Maven项目添加依赖即可
<!--Mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
然后添加数据库连接类,此步移交JDBC笔记
事务
详见SQL笔记

浙公网安备 33010602011771号