使用纯Servlet写oa项目会发现,
1、java程序中编写前端代码,编写难度大,麻烦
2、java程序中编写前端代码,不美观,代码不简洁
3、java程序中编写前端代码,前后端不分离,程序耦合度低
4、java程序中编写前端代码,维护成本高。
若前端代码改动一点,java程序就需要改动,重新编译,生成一个新的class文件,然后再重新打包成一个war包,重新发布。
既然如此,我们如何解决上述问题呢?
那必然是使得前后端尽量分离,不在java程序中写前端代码。换句话说就是我们写的前端代码不在java程序中,但是计算机可以将我们写的前端代码转换/翻译为Servlet那种的java程序,然后再进行编译,生成class文件。
java程序负责后端代码,仅做收集数据的工作,针对数据展示不负责。
因此我们可以采用servlet+jsp,Servlet java程序做收集数据,jsp做数据展示。
jsp相关知识点补充
1、JSP实际上是一个Servlet
2、对JSP进行错误调试的时候,还是要直接打开JSP文件对应的java文件,检查java代码。
3、JSP的基础语法
- 在jsp文件中直接编写文字,都会自动被翻译到哪里?
- 翻译到servlet类的service方法的out.write(“翻译到这里”),直接翻译到双引号里,被java程序当做普通字符串打印输出到浏览器。
- 在JSP中编写的HTML CSS JS代码,这些代码对于JSP来说只是一个普通的字符串。但是JSP把这个普通的字符串一旦输出到浏览器,浏览器就会对HTML CSS JS进行解释执行。展现一个效果。
4、Jsp编写java程序
<%java程序;%> 会被翻译到Servlet 类的service方法内部
<%! %> 会被翻译到Servlet类的service方法外部
<%=java程序%> 等价于在service方法内部的 out.print(java程序)
<!-- --> jsp的专业注释
<%@page contentType="text/html;charset=UTF-8"%>
- page指令,通过contentType属性用来设置响应的内容类型。
5、jsp的九大内置对象
- jakarta.servlet.jsp.PageContext pageContext 页面作用域
- jakarta.servlet.http.HttpServletRequest request 请求作用域
- jakarta.servlet.http.HttpSession session 会话作用域
- jakarta.servlet.ServletContext application 应用作用域
- pageContext < request < session < application
- 以上四个作用域都有:setAttribute、getAttribute、removeAttribute方法。
- 以上作用域的使用原则:尽可能使用小的域。
- java.lang.Throwable exception
- jakarta.servlet.ServletConfig config
- java.lang.Object page (其实是this,当前的servlet对象)
- jakarta.servlet.jsp.JspWriter out (负责输出)
- jakarta.servlet.http.HttpServletResponse response (负责响应)
6、jsp指令
- 指令的作用:指导JSP的翻译引擎如何工作(指导当前的JSP翻译引擎如何翻译JSP文件。)
- 指令包括哪些呢?
- include指令:包含指令,在JSP中完成静态包含,很少用了。(这里不讲)
- taglib指令:引入标签库的指令。这个到JJSTL标签库的时候再学习。现在先不管。
- page指令:目前重点学习一个page指令。
- 指令的使用语法是什么?
- <%@指令名 属性名=属性值 属性名=属性值 属性名=属性值…%>
- 关于page指令当中都有哪些常用的属性呢?
<%@page session="true|false" %>
true表示启用JSP的内置对象session,表示一定启动session对象。没有session对象会创建。
如果没有设置,默认值就是session="true"
session="false" 表示不启动内置对象session。当前JSP页面中无法使用内置对象session。
<%@page contentType="text/json" %>
contentType属性用来设置响应的内容类型
但同时也可以设置字符集。
<%@page contentType="text/json;charset=UTF-8" %>
<%@page pageEncoding="UTF-8" %>
pageEncoding="UTF-8" 表示设置响应时采用的字符集。
<%@page import="java.util.List, java.util.Date, java.util.ArrayList" %>
<%@page import="java.util.*" %>
import语句,导包。
<%@page errorPage="/error.jsp" %>
当前页面出现异常之后,跳转到error.jsp页面。
errorPage属性用来指定出错之后的跳转位置。
<%@page isErrorPage="true" %>
表示启用JSP九大内置对象之一:exception
默认值是false。
7、使用Servlet + JSP完成oa项目的改造。
使用Servlet处理业务,收集数据。 使用JSP展示数据。
- 将之前原型中的html文件,全部修改为jsp,然后在jsp文件头部添加page指令(指定contentType防止中文乱码),将所有的JSP直接拷贝到web目录下。
- 完成所有页面的正常流转。(页面仍然能够正常的跳转。修改超链接的请求路径。)
- <%=request.getContextPath() %> 在JSP中动态的获取应用的根路径。
- Servlet中连接数据库,查询所有的部门,遍历结果集。
- 遍历结果集的过程中,取出部门编号、部门名、位置等信息,封装成java对象。
- 将java对象存放到List集合中。 list.add(student)
- 将List集合存储到request域当中。request.setAttribute(list);
- 转发forward到jsp。request.getDirectorforward(/list.jsp);
- 在JSP中:
- 从request域当中取出List集合。
- 遍历List集合,取出每个部门对象。动态生成。
写代码时可以发现在进行数据展示时,list.jsp等其他jsp页面代码复杂,看着不美观,费劲,因此我们使用EL表达式来代替sp代码,使得页面更加美观整洁。
EL表达式相关知识点
1、el表达式实质上是jsp代码
2、EL表达式的功效是 从某个作用域中取数据,然后再将该数据转换成字符串,再将该字符串输出到浏览器上面。
- 三大功效:
- 第一功效:从某个域中取数据。
- 四个域:
- pageContext
- request
- session
- application
- 第二功效:将取出的数据转成字符串。
- 如果是一个java对象,也会自动调用java对象的toString方法将其转换成字符串。
- 第三功效:将字符串输出到浏览器。
- 和这个一样:<%= %>,将其输出到浏览器。
3、EL表达式语法格式 ${表达式}
4、EL表达式如何代替jsp代码?
for instance
User user1 = new User(“张三”,18);
request.setAttribute("userobj",user1);
<%=request.getAttribute("userobj")%>
以上代码块可以用EL表达式替代为
${userobj}
面试题:
${abc} 和 ${“abc”}的区别是什么?
${abc}表示从某个域中取出数据,并且被取的这个数据的name是"abc",之前一定有这样的代码: 域.setAttribute(“abc”, 对象);
${“abc”} 表示直接将"abc"当做普通字符串输出到浏览器。不会从某个域中取数据了。
${userObj} 底层是怎么做的?从域中取数据,取出user对象,然后调用user对象的toString方法,转换成字符串,输出到浏览器。
<%–如果想输出对象的属性值,怎么办?–%>
${userObj.username} 使用这个语法的前提是:User对象有getUsername()方法。
${userObj.password} 使用这个语法的前提是:User对象有getPassword()方法。
${userObj.age} 使用这个语法的前提是:User对象有getAge()方法。
${userObj.email} 使用这个语法的前提是:User对象有getEmail()方法。
EL表达式中的. 这个语法,实际上调用了底层的getXxx()方法。
注意:如果没有对应的get方法,则出现异常。报500错误。
${userObj.addr222.zipcode}
以上EL表达式对应的java代码:
user.getAddr222().getZipcode()
5、EL表达式优先从小范围读取
pagecontext<request<seesion<application
但是EL表达式中有四个隐含的隐式的范围:
- pageScope 对应的是 pageContext范围。
- requestScope 对应的是 request范围。
- sessionScope 对应的是 session范围。
- applicationScope 对应的是 application范围。
6、EL表达式取数据的两种方式:
(1){abc.def};因为计算机会误认为是对象abc的getdef()方法。因此我们需要用EL表达式的另一种方式获取数据。[“”]
${requestcope[“abc.def”]},就可以获取name=abc.def的key
7、EL表达式的其他用法 - age指令当中,有一个属性,可以忽略EL表达式
<%@page contentType="text/html;charset=UTF-8" isELIgnored="true" %>
isELIgnored="true" 表示忽略EL表达式
isELIgnored="false" 表示不忽略EL表达式。(这是默认值)
isELIgnored="true" 这个是全局的控制。
可以使用反斜杠进行局部控制:\${username} 这样也可以忽略EL表达式。
- 通过EL表达式获取应用的根:
- ${pageContext.request.contextPath}
- EL表达式中其他的隐式对象:
- pageContext
- param
- paramValues
- initParam
- EL表达式的运算符
- 算术运算符
- +、-、*、/、%
- 关系运算符
- 逻辑运算符
- 条件运算符
- 取值运算符
- [ ]和.
- empty运算符
虽然我们使用EL表达式代替了jsp代码,但是我们知道,jsp技术现在基本上很少用了,或者说只有一些老项目还在用,市面上基本上不咋用了。所以现在我们进一步改进oa项目,运用JSTL标签库和EL表达式结合,使得jsp代码消失。
JSTL标签库相关知识点
1、JSTL标签是写在jsp代码中的,但是实际上执行的还是java程序。该java程序在jar包中。
2、使用JSTL标签库的步骤
- 第一步:引入JSTL标签库对应的jar包。
- tomcat10之后引入的jar包是:
- jakarta.servlet.jsp.jstl-2.0.0.jar
- jakarta.servlet.jsp.jstl-api-2.0.0.jar
- 在IDEA当中怎么引入?
- 在WEB-INF下新建lib目录,然后将jar包拷贝到lib当中。然后将两个jar包“Add as Lib…”
- 一定是要和mysql的数据库驱动一样,都是放在WEB-INF/lib目录下的。
提问:什么时候需要将jar包放到WEB-INF/lib目录下?
解答:如果这个jar是tomcat服务器没有的。以上的两个JSTL标签库的jar包和连接数据库的connector都是小猫咪没有的。像servlet api和jsp api这两个jar包是小猫咪有的,那么就把他们直接在project structure里面添加依赖即可。
- 第二步:在JSP中引入要使用标签库。(使用taglib指令引入标签库。)
- JSTL提供了很多种标签,你要引入哪个标签????重点掌握核心标签库。
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
这个就是核心标签库。
prefix="这里随便起一个名字就行了,核心标签库,大家默认的叫做c,你随意。"
- 第三步:在需要使用标签的位置使用即可。表面使用的是标签,底层实际上还是java程序。
3、JSTL标签库原理
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
以上uri后面的路径实际上指向了一个xxx.tld文件。
tld文件实际上是一个xml配置文件。
在tld文件中描述了“标签”和“java类”之间的关系。
以上核心标签库对应的tld文件是:c.tld文件。
它在哪里。
在jakarta.servlet.jsp.jstl-2.0.0.jar里面META-INF目录下,
有一个c.tld文件。
源码分析:
配置文件c.tld文件
<tag>
<description>对该标签的描述</description>
<name>catch</name> 标签的名字
<tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class> 标签对应的java类。
<body-content>JSP</body-content> 标签体当中可以出现的内容,如果是JSP,就表示标签体中可以出现符合JSP所有语法的代码。例如EL表达式。
<attribute>
<description>
对这个属性的描述
</description>
<name>var</name> 属性名
<required>false</required> false表示该属性不是必须的。true表示该属性是必须的。
<rtexprvalue>false</rtexprvalue> 这个描述说明了该属性是否支持EL表达式。false表示不支持。true表示支持EL表达式。
</attribute>
</tag>
<c:catch var="">
JSP....
</c:catch>
4、JSTL标签库的常用标签
<%@taglib prefix="c" uri="http:java/sun/ "%>
(1)<c:if test="boolean,支持EL表达式" > </c:if>
(2)<c:forEach items="集合,支持EL表达式" var="集合中的元素" varstatus="元素状态对象">${元素状态对象.count}</c:forEach>
<c:forEach var="i" begin="1" end="10" step="2"> ${i} </c: forEach>
(3)<c:choose>
<c:when test="${param.age < 18}">
青少年
</c:when>
<c:when test="${param.age < 35}">
青年
</c:when>
<c:when test="${param.age < 55}">
中年
</c:when>
<c:otherwise>
老年
</c:otherwise>
</c:choose>
关于B/S系统的会话机制(session机制)
1、什么是会话
用户打开浏览器到关闭浏览器称作一次会话。
2、一次会话过程
用户打开浏览器的时候,服务器会自动创建seesion对象和sessionid,然后服务器会将该sessionid响应给浏览器,浏览器将sessionid(cookie)存起来,存在运行时内存中。在同一次会话中,当用户发送请求时浏览器可以根据缓存中的sessionid是否对号判断该sessionid对应的会话是否还存在。当关闭浏览器时,浏览器正在运行内存释放,也就是说浏览器存的seesionid没了。但注意这并不意味着session对象没了,因为浏览器遵循的是http协议,http协议是无状态协议,请求结束,B和S连接断开,所以服务器不知道用户什么时候关闭的浏览器。
那么session对象何时被销毁呢?
session对象有一个超时机制,可以设置session对象何时销毁,默认情况下session对象半个小时后销毁。
- 张三打开一个浏览器A,李四打开一个浏览器B,访问服务器之后,在服务器端会生成:
- 张三专属的session对象
- 李四专属的session对象
3、会话对象session保存在服务器端,存在运行中内存中。
4、为什么需要session来保持会话状态呢?
(1)HTTP是无状态协议
什么是无状态呢?就是请求的时候,B和S是连接的,请求结束之后,B和S的连接就断开了。为什么要这么做?HTTP协议为什么要设计成这样?因为这样的无状态协议,可以降低服务器的压力。请求的瞬间是连接的,请求结束之后,连接断开,这样服务器压力小。
(2)为什么不采用request/ServletContext来抱回会话状态呢?
- request.setAttribute()存,request.getAttribute()取,ServletContext也有这个方法。request是请求域。ServletContext是应用域。
- request是一次请求一个对象。
- ServletContext对象是服务器启动的时候创建,服务器关闭的时候销毁,这个ServletContext对象只有一个。
- ServletContext对象的域太大。
- request请求域(HttpServletRequest)、session会话域(HttpSession)、application域(ServletContext)
- request < session < application
5、session对象实现原理
HttpSession session = request.getSession();
这行代码很神奇。张三访问的时候获取的session对象就是张三的。李四访问的时候获取的session对象就是李四的。
- JSESSIONID=xxxxxx 这个是以Cookie的形式保存在浏览器的内存中的。浏览器只要关闭。这个cookie就没有了。
- session列表是一个Map,map的key是sessionid,map的value是session对象。
- 用户第一次请求,服务器生成session对象,同时生成id,将id发送给浏览器。
- 用户第二次请求,自动将浏览器内存中的id发送给服务器,服务器根据id查找session对象。
- 关闭浏览器,内存消失,cookie消失,sessionid消失,会话等同于结束。
也就是说cookie可以视为sessionid,那么如果Cookie禁用了,session还能找到吗?
- cookie禁用是什么意思?服务器正常发送cookie给浏览器,但是浏览器不要了。拒收了。并不是服务器不发了。
- 找不到了。每一次请求都会获取到新的session对象。
- cookie禁用了,session机制还能实现吗?
- 可以。需要使用URL重写机制。
- http://localhost:8080/servlet12/test/session;jsessionid=19D1C99560DCBF84839FA43D58F56E16
- URL重写机制会提高开发者的成本。开发人员在编写任何请求路径的时候,后面都要添加一个sessionid,给开发带来了很大的难度,很大的成本。所以大部分的网站都是这样设计的:你要是禁用cookie,你就别用了。
Cookie相关知识点
1、一个session对象对应一个sessionid,一个sessionid对应一个cookie
JSESSIONID=41C481F0224664BDB28E95081D23D5B8
这个键值对就是cookie对象
- 对于session关联的cookie来说,这个cookie是被保存在浏览器的“运行内存”当中。
- 只要浏览器不关闭,用户再次发送请求的时候,会自动将运行内存中的cookie发送给服务器。
- cookie怎么生成?cookie保存在什么地方?cookie有啥用?浏览器什么时候会发送cookie,发送哪些cookie给服务器???????
- cookie最终是保存在浏览器客户端上的。
- 可以保存在运行内存中。(浏览器只要关闭cookie就消失了。)
- 也可以保存在硬盘文件中。(永久保存。)
- cookie有啥用呢?
- cookie和session机制其实都是为了保存会话的状态。
- cookie是将会话的状态保存在浏览器客户端上。(cookie数据存储在浏览器客户端上的。硬盘文件中)
- session是将会话的状态保存在服务器端上。(session对象是存储在服务器上。)
2、cookie机制和session机制其实都不属于java中的机制,实际上cookie机制和session机制都是HTTP协议的一部分。php开发中也有cookie和session机制,只要是你是做web开发,不管是什么编程语言,cookie和session机制都是需要的。
3、- HTTP协议中规定:任何一个cookie都是由name和value组成的。name和value都是字符串类型的。
- 在java的servlet中,对cookie提供了哪些支持呢?
- 提供了一个Cookie类来专门表示cookie数据。
- jakarta.servlet.http.Cookie;
- java程序怎么把cookie数据发送给浏览器呢?
- response.addCookie(cookie);
在HTTP协议中是这样规定的:当浏览器发送请求的时候,会自动携带该path下的cookie数据给服务器。(URL。)
Fliter过滤器
- Filter可以在Servlet这个目标程序执行之前添加代码。也可以在目标Servlet执行之后添加代码。之前之后都可以添加过滤规则。
- 一般情况下,都是在过滤器当中编写公共代码。
1、一个过滤器怎么写呢?
- 第一步:编写一个Java类实现一个接口:jarkata.servlet.Filter。并且实现这个接口当中所有的方法。
- init方法:在Filter对象第一次被创建之后调用,并且只调用一次。
- doFilter方法:只要用户发送一次请求,则执行一次。发送N次请求,则执行N次。在这个方法中编写过滤规则。
- destroy方法:在Filter对象被释放/销毁之前调用,并且只调用一次。
- 第二步:在web.xml文件中对Filter进行配置。这个配置和Servlet很像。
<filter>
<filter-name>filter2</filter-name>
<filter-class>com.bjpowernode.javaweb.servlet.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
- 或者使用注解:@WebFilter({“*.do”})
- 注意:
- Servlet对象默认情况下,在服务器启动的时候是不会新建对象的。
- Filter对象默认情况下,在服务器启动的时候会新建对象。
- Servlet是单例的。Filter也是单例的。(单实例。)
- 目标Servlet是否执行,取决于两个条件:
- 第一:在过滤器当中是否编写了:chain.doFilter(request, response); 代码。
- 第二:用户发送的请求路径是否和Servlet的请求路径一致。
- chain.doFilter(request, response); 这行代码的作用:
- 执行下一个过滤器,如果下面没有过滤器了,执行最终的Servlet。
- 注意:Filter的优先级,天生的就比Servlet优先级高。
- /a.do 对应一个Filter,也对应一个Servlet。那么一定是先执行Filter,然后再执行Servlet。
- 关于Filter的配置路径:
- /a.do、/b.do、/dept/save。这些配置方式都是精确匹配。
- /* 匹配所有路径。
- *.do 后缀匹配。不要以 / 开始
- /dept/* 前缀匹配。
- 在web.xml文件中进行配置的时候,Filter的执行顺序是什么?
- 依靠filter-mapping标签的配置位置,越靠上优先级越高。
- 过滤器的调用顺序,遵循栈数据结构。
- 使用@WebFilter的时候,Filter的执行顺序是怎样的呢?
- 执行顺序是:比较Filter这个类名。
- 比如:FilterA和FilterB,则先执行FilterA。
- 比如:Filter1和Filter2,则先执行Filter1.
- Filter的生命周期?
- 和Servlet对象生命周期一致。
- 唯一的区别:Filter默认情况下,在服务器启动阶段就实例化。Servlet不会。
- Filter过滤器这里有一个设计模式:
- 责任链设计模式。
- 过滤器最大的优点:
- 在程序编译阶段不会确定调用顺序。因为Filter的调用顺序是配置到web.xml文件中的,只要修改web.xml配置文件中filter-mapping的顺序就可以调整Filter的执行顺序。显然Filter的执行顺序是在程序运行阶段动态组合的。那么这种设计模式被称为责任链设计模式。
- 责任链设计模式最大的核心思想:
- 在程序运行阶段,动态的组合程序的调用顺序。
- 使用过滤器改造OA项目。
Listener监听器
- 什么是监听器?
- 监听器是Servlet规范中的一员。就像Filter一样。Filter也是Servlet规范中的一员。
- 在Servlet中,所有的监听器接口都是以“Listener”结尾。
- 监听器有什么用?
- 监听器实际上是Servlet规范留给我们javaweb程序员的特殊时机。
- 特殊的时刻如果想执行这段代码,你需要想到使用对应的监听器。
- Servlet规范中提供了哪些监听器?
- jakarta.servlet包下:
- ServletContextListener
- ServletContextAttributeListener
- ServletRequestListener
- ServletRequestAttributeListener
- jakarta.servlet.http包下:
- HttpSessionListener
- HttpSessionAttributeListener
- 该监听器需要使用@WebListener注解进行标注。
- 该监听器监听的是什么?是session域中数据的变化。只要数据变化,则执行相应的方法。主要监测点在session域对象上。
- HttpSessionBindingListener
- 该监听器不需要使用@WebListener进行标注。
- 假设User类实现了该监听器,那么User对象在被放入session的时候触发bind事件,User对象从session中删除的时候,触发unbind事件。
- 假设Customer类没有实现该监听器,那么Customer对象放入session或者从session删除的时候,不会触发bind和unbind事件。
- HttpSessionIdListener
- session的id发生改变的时候,监听器中的唯一一个方法就会被调用。
- HttpSessionActivationListener
- 监听session对象的钝化和活化的。
- 钝化:session对象从内存存储到硬盘文件。
- 活化:从硬盘文件把session恢复到内存。
- 实现一个监听器的步骤:以ServletContextListener为例。
- 第一步:编写一个类实现ServletContextListener接口。并且实现里面的方法。
void contextInitialized(ServletContextEvent event)
void contextDestroyed(ServletContextEvent event)
- 第二步:在web.xml文件中对ServletContextListener进行配置,如下:
<listener>
<listener-class>com.bjpowernode.javaweb.listener.MyServletContextListener</listener-class>
</listener>
- 当然,第二步也可以不使用配置文件,也可以用注解,例如:@WebListener
- 注意:所有监听器中的方法都是不需要javaweb程序员调用的,由服务器来负责调用?什么时候被调用呢?
- 当某个特殊的事件发生(特殊的事件发生其实就是某个时机到了。)之后,被web服务器自动调用。
- 思考一个业务场景:
- 请编写一个功能,记录该网站实时的在线用户的个数。
- 我们可以通过服务器端有没有分配session对象,因为一个session代表了一个用户。有一个session就代表有一个用户。如果你采用这种逻辑去实现的话,session有多少个,在线用户就有多少个。这种方式的话:HttpSessionListener够用了。session对象只要新建,则count++,然后将count存储到ServletContext域当中,在页面展示在线人数即可。
- 业务发生改变了,只统计登录的用户的在线数量,这个该怎么办?
- session.setAttribute(“user”, userObj);
- 用户登录的标志是什么?session中曾经存储过User类型的对象。那么这个时候可以让User类型的对象实现HttpSessionBindingListener监听器,只要User类型对象存储到session域中,则count++,然后将count++存储到ServletContext对象中。页面展示在线人数即可。
- 实现oa项目中当前登录在线的人数。
- 什么代表着用户登录了?
- session.setAttribute(“user”, userObj); User类型的对象只要往session中存储过,表示有新用户登录。
- 什么代表着用户退出了?
- session.removeAttribute(“user”); User类型的对象从session域中移除了。
- 或者有可能是session销毁了。(session超时)