12.JSP ( java server page)
昨日所学:servlet代码可处理前端发来的请求,且可通过输出流往前端输出页面。
问题:所有输出的内容都是手写,若输出结构复杂的页面很麻烦。
官方提供了另一种组件jsp,可简化页面输出的这部分。
示例如下:
①.新建项目
Project Structure--->左上角加号--->New Module---->java Enterprise--->勾选Web Application---->Module name为day3_jsp
②.web包下默认存在一个index.jsp,可自己新建一个myPage.jsp
~~~
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html>
<head> <title>Title</title> </head>
<body> 我的页面 </body> </html> ~
③.点击右上角文本框,选择Edit....,进入Run/Debug Configurations界面
选择Deployment,将Application context的内容改为/day3
选择Server,将URL内容改为http://localhost:8080/day3/myPage.jsp,让页面直接弹出它
④.重新部署,启动服务器
页面直接弹出:我的页面
看起来和昨天的myPage.html很像,静态页面也是在web包下创建,访问路径也一样,也是用html标签。
访问这个目录Using CATALINA_BASE: "C:\Users\JAVASM\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat_javaEE"
之前idea会把Tomcat里的配置文件(conf)目录和日志(logs)在这里拷一份,还有一个work目录。在work目录里,work\Catalina\localhost\day3\org\apache\jsp目录下会有myPage_jsp.class和myPage_jsp.java两个文件。
打开myPage_jsp.java,有_jspService方法
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final java.lang.String _jspx_method = request.getMethod(); if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET POST or HEAD"); return; }
参数一个是request,一个是response。分别给出了get,post,head方式的处理方式。
还会发现:
out.write("\r\n"); out.write("\r\n"); out.write("<html>\r\n"); out.write("<head>\r\n"); out.write(" <title>Title</title>\r\n"); out.write("</head>\r\n"); out.write("<body>\r\n"); out.write(" 我的页面\r\n"); out.write("</body>\r\n"); out.write("</html>\r\n");
实际上,jsp会自动转成一种类似于servlet的结构。使用的类和方法和servlet有些不同,但运行机制和servlet相同。
故:myPage.jsp实际上是提供了一个模板,可将要输出的内容以html标签的形式写入。之后该文件会转成一个类似servlet的结构,将页面输出。
(jsp内部有一个内置转换,看到的.jsp文件不是直接运行的,首先将jsp文件转成java文件(myPage_jsp.java),再编译成class文件(myPage_jsp.class),再去执行。)
1.jsp本质
jsp也是一个servlet
myPage.jsp ---> myPage_jsp.java ----> myPage_jsp.class 翻译 编译 执行 jsp转化成servlet的标准去执行 jsp本质上 也是servelt 服务器自动转化成servlet (同样有servlet方法,request和response对象。但和自己写的servlet有几点不同:) 1.服务器自动配置请求地址 /myPage.jsp (自己写的servlet,请求地址自己配。jsp的请求地址服务器自动配置,以文件所在的真实目录来模拟该地址。) 2.先翻译成servlet执行 在jsp中写的文本和标签 会转成输出流的输出代码 3.当请求访问到指定的jsp文件时 服务器才会执行翻译>编译>运行的过程。才会在work目录生成一系列目录和文件。 (所有第一次访问很慢,要做很多工作。但之后,各种目录已建立,访问会快很多。)
jsp介绍:
1.JSP(Java Server Page)是JavaEE规范中的Web组件,用来编写动态页面;
2.JSP文件以.jsp为后缀,在Eclipse的工程目录中存在WebContent目录下,跟静态资源使用方式非常相似;
3.JSP文件中的内容就是 HTML+Java代码,静态部分使用HTML和文本即可,动态部分使用Java代码;
2.jsp写代码的格式
html+java代码
jsp中允许使用的java代码格式 1.指令 <%@ 指令名 key="value" %> page指令 配置当前页面 contentType="text/html;charset=UTF-8" language="java" import="" include指令 可以把其他页面引入进来 1.公共的html 2.公共的java代码 taglib 指令 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2.java脚本 <% %> 可以有多段 经常与输出的部分交叉编写 3.输出变量 <%= users.getUserName() %> 4.声明方法 <%! %> 5.注释 <%-- --%> jsp注释 只出现在jsp源文件中 <!-- --> html注释 是html中隐藏的文本
2.1.指令
1.Page指令
自动生成的页面第一行会有page指令,后面是响应头,语言
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
import属性,做导包用
<%@ Page import="java.util.concurrent.TimeUnit" %>
(Page指令一个文件里可以写多条)
2.include指令
可以把其他页面引入进来
<%@include file="路径名"%>
被引入页面的html相关公共标签,java公共代码也会被带进去。
被引入的页面没必要写公共html标签,只把有用的部分写出来就行,比如:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <h1>我的标题</h1>
include指令,一般就两种用法,一种是写一些公共的html(也是jsp文件),让其他jsp引用。
另一种就是建一些公共的java代码,让其他页面引用:
比如web.index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String a = "abc123"; %>
web.pages.myPage.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <%@include file="../index.jsp"%> <%--引入公共变量--%> <% out.write(a);%> <%-- 输出变量--%> </body> </html>
2.2java脚本
<% %> 在两个%间写java代码
我的页面 <% Integer a = 1; Integer b = 2; out.print(a+b); /*打印输出*/ %>
jsp转换之后,就是(普通文本会加上out.write() 带<%%>转换后就去掉<%%>)
out.write("我的页面"); Integer a = 1; Integer b = 2; out.print(a+b);
故,在写一些带有输出的java代码时,还能简化,比如如下逻辑判断:
<% Boolean flag = true; if(flag){%> <h1>执行成功</h1>xxxx <%}else{%> <h1>执行失败</h1>xxxx <%}%>
jsp转成servlet后的代码:
Boolean flag = true; if(flag){ out.write("\r\n"); out.write("<h1>执行成功</h1>xxxx \r\n"); }else{ out.write("\r\n"); out.write("<h1>执行失败</h1>xxxx \r\n"); }
页面里会经常用到输出的代码
<%out.print(a+b);%>
jsp里有简化的写法:
<%=a+b%>
不加分号,jsp会转成:
out.print(a+b);
2.3.声明方法
前面已知,整个代码都包含在_jspService方法里。
方法里再定义方法很少,也很麻烦。应该在该方法外定义方法。
语法:<%! %>
示例:
<%! Integer getSum(Integer a,Integer b){ return a+b; }%> <%--调用该方法--%> <% Integer sum = getSum(2, 3); %> <%=sum %>
2.4注释
语法:<%-- --%> 叫做 jsp注释,是真的注释,只出现在jsp源文件中。(转成servlet后就看不到jsp源文件注释的内容了)
<!-- --> 是 html注释,它是页面里一种特殊的元素, 是html中隐藏的文本
3.内置对象 作用域
9种内置对象 1.request 请求对象 2.response 响应对象 3.session 会话对象 4.application 服务器对象(ServletContext) 5.out 输出流对象 6.pageContext 页面相关的综合对象 7.config servletConfig 读初始参数 读servlet配置参数 8.page 当前页面 this 9.exception 异常信息设置 4种作用域 (域对象) pageContext 当前页面域 request request域 session session域 application servletContext域
3.1 什么是的内置对象
(jsp的本质是servlet)
1.在jsp开发中会频繁使用到一些对象.如果每次我们在jsp页面中需要使用这些对象都要自己亲自动手创建就会特别的繁琐,SUN公司因此在设计jsp时,在jsp页面加载完毕之后自动帮开发者创建好了这些对象,开发者只需要使用相应的对象调用相应的方法即可。这些系统创建好的对象就叫做内置对象.
2.Jsp文件翻译成java文件时,有一些对象被自动写在了java文件中,这些被自动写进去的对象叫内置对象。
eg:上面用到的out,并没有定义出来,但可以用。因为jsp转换成servlet后,_jspService方法里已经有了out的定义:
final javax.servlet.jsp.PageContext pageContext; javax.servlet.jsp.JspWriter out = null; out=pageContext.getOut();
故,out实际是输出流对象。
这些jsp里没定义,但最终翻译出的代码里已经自动定义的对象就是内置对象。
(PageContext是jsp里单独创建出的一个类型对象。是一个综合对象)
3.2常用的内置对象
有九个
1.request对象
request 对象是 javax.servlet.httpServletRequest类型的对象。 该对象代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的数据。(包括头信息、系统信息、请求方式以及请求参数等)。request对象的作用域为一次请求。
jsp里使用请求对象,必须用request。
示例:
新建web.pages.myPage2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <% String mykey = request.getParameter("mykey");%> <%=mykey%> </body> </html>
重新部署,弹出的页面输入http://localhost:8080/day3/pages/myPage2.jsp?mykey=jack
结果:页面输出jack
final javax.servlet.http.HttpServletRequest request
2.response对象
response 代表的是对客户端的响应,主要是将JSP容器处理过的对象传回到客户端。response对象也具有作用域,它只在JSP页面内有效。
响应对象
final javax.servlet.http.HttpServletResponse response
3.session对象
session 对象是由服务器自动创建的与用户请求相关的对象。服务器为每个用户都生成一个session对象,用于保存该用户的信息,跟踪用户的操作状态。session对象内部使用Map类来保存数据,因此保存数据的格式为 “Key/value”。 session对象的value可以使用复杂的对象类型,而不仅仅局限于字符串类型。
会话对象。
javax.servlet.http.HttpSession session = null;
4.application对象
application 对象可将信息保存在服务器中,直到服务器关闭,否则application对象中保存的信息会在整个应用中都有效。与session对象相比,application对象生命周期更长,类似于系统的“全局变量”。
类型是ServletContext,表示服务器对象。在jsp里换了个名字。
final javax.servlet.ServletContext application;
5.out 对象
out 对象用于在Web浏览器内输出信息,并且管理应用服务器上的输出缓冲区。在使用 out 对象输出数据时,可以对数据缓冲区进行操作,及时清除缓冲区中的残余数据,为其他的输出让出缓冲空间。待数据输出完毕后,要及时关闭输出流。
输出流对象
6.pageContext 对象
pageContext 对象的作用是取得任何范围的参数,通过它可以获取 JSP页面的out、request、reponse、session、application 等对象。pageContext对象的创建和初始化都是由容器来完成的,在JSP页面中可以直接使用 pageContext对象。
jsp转servlet后的内部代码:
final javax.servlet.jsp.PageContext pageContext; pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out;
PageContext(把page里相关的对象综合),ServletContext(服务器对象,和servlet相关的一些对象的综合)。 Context翻译是上下文
编程中Context用环境更合适,是一些对象的综合。
7.config 对象
config 对象的主要作用是取得服务器的配置信息。通过 pageConext对象的 getServletConfig() 方法可以获取一个config对象。当一个Servlet 初始化时,容器把某些信息通过 config对象传递给这个 Servlet。 开发者可以在web.xml 文件中为应用程序环境中的Servlet程序和JSP页面提供初始化参数。
servlet里代码里可直接使用this代替,代表ServletConfig接口。可以拿初始化参数,也可拿ServletContext对象。
8.page 对象
page 对象代表JSP本身,只有在JSP页面内才是合法的。 page隐含对象本质上包含当前 Servlet接口引用的变量,类似于Java编程中的 this
final java.lang.Object page = this;
jsp文件里,this没有指代ServletConfig,而是指代page,当前页面本身的对象。
this指代发生变化,故单独在7中出现config 对象。
9.exception 对象
exception 对象的作用是显示异常信息,只有在包含 isErrorPage="true" 的页面中才可以被使用,在一般的JSP页面中使用该对象将无法编译JSP文件。excepation对象和Java的所有对象一样,都具有系统提供的继承结构。exception 对象几乎定义了所有异常情况。在Java程序中,可以使用try/catch关键字来处理异常情况; 如果在JSP页面中出现没有捕获到的异常,就会生成 exception 对象,并把 exception 对象传送到在page指令中设定的错误页面中,然后在错误页面中处理相应的 exception 对象。
一般Tomcat会把异常信息打印到页面,故该对象一般不用。
3.3 四种作用域
使用的对象 作用域名
ServletContext application域
HttpServletRequet request域
HttpSession session域
PageContext page域
实际上就是域对象。之前讲的域对象是请求访问的过程中产生的,一共三个:
1.request 每次请求产生一个独立的request域
2.session 同一个浏览器多次访问使用同一个session
3.servletContext 全局共享域
jsp也可使用域对象,但需用内置对象调用。比如:ServletContext域要使用application域去调。
多出一个page域,在当前jsp文件里可使用。
扩展:jsp实际上就是一个servlet。它可以取请求参数,写业务逻辑代码,引包,写逻辑控制,写输出代码....
那还有必要使用servlet吗?
如果把所有的功能写到同一个页面,这种语言叫PHP(语法和java不太一样)。PHP语言被称为“世界最好的语言”,常用在中小型企业,因为,语法很简单,特别好上手。java里也能这样写,但不这样做是因为维护的问题。
中小型企业应用和大型企业应用最主要的区别:大型企业应用一般有版本的迭代更新,要长期使用。
写servlet时只是拼页面比较麻烦,请求数据的接收,对象的封装,业务逻辑调用等还是用servlet写,当需要输出页面时,拿jsp帮助输出。
接下来讲servlet和jsp的关联。
4.servlet+jsp 配合
4.1. Jsp与servlet分工
Servlet与jsp本质上是一个东西,但是在写法上各有优势,servlet中写java代码方便,jsp编写页面显示内容方便,所以一般会由servlet来负责代码的调用,由jsp负责数据的显示,那么会需要将servlet中处理的数据,传递到jsp上。
servlet 负责java部分
jsp 负责显示部分
4.2跳转方式
将不同的servlet连接起来的方式,jsp本质也是servlet。
主要有以下两种方式,它们的做法不同,调用的对象也不同。
响应 重定向
resp.sendRedirect(req.getContextPath()+"/pages/showRes.jsp?username="+username); 浏览器 多次请求 request域中数据不能共享 浏览器地址栏 停留在重定向的地址上 (jsp) 可以使用外部链接 相对路径 相对根路径resp.sendRedirect(req.getContextPath()+"/pages/showRes.jsp); 绝对路径 如果想共享数据 可以在url后拼接参数请求 转发
req.getRequestDispatcher("/pages/showRes.jsp").forward(req,resp); 浏览器 一次请求 request域中数据可以共享 浏览器地址栏 停留在原始访问地址上 (servlet) 内部进行转发 相对路径 相对根路径 会自动拼项目名 req.getRequestDispatcher("/pages/2JSTLDemo.jsp").forward(req,resp);
1.响应重定向
(重定向也叫响应重定向)
响应重定向通过response对象调用,让浏览器向重定向的地址重新发送一次请求response.sendRedirect("error.jsp");
注意:是因为重定向会发送新的请求,会导致第一次请求的相关数据在第二次请求中读取不到,如果我们需要这些数据可以使用转发的跳转方式。
示例:
①.新建web.pages.showRes.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> 显示结果的页面 </body> </html>
②.新建com.javasm.controller包
包下新建MyServlet类
@WebServlet("/demo1") public class MyServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //接收参数 //调用逻辑代码 System.out.println("业务逻辑走完了"); //接下来是输出页面,不用输出流,用跳转方式去调用jsp执行 //1.响应重定向 resp.sendRedirect(要访问的地址); resp.sendRedirect("pages/showRes.jsp"); } }
③.测试
重新部署,运行,弹出的页面输入http://localhost:8080/day3/demo1,回车
页面会跳转到http://localhost:8080/day3/pages/showRes.jsp,并显示 显示结果的页面
2.请求转发
(转发也叫请求转发)
请求转发通过request对象调用,把请求对象和响应对象与要跳转的地址共享request.getRequestDispatcher("showIP.jsp").forward(request, response);
示例:
①.MyServlet类
@WebServlet("/demo1") public class MyServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //接收参数 //调用逻辑代码 System.out.println("业务逻辑走完了"); //接下来是输出页面,不用输出流,用跳转方式去调用jsp执行 //2.(请求)转发 Request请求 Dispatcher转发器 获得请求转发器 //req.getRequestDispatcher(要访问的地址) req.getRequestDispatcher("pages/showRes.jsp").forward(req,resp); } }
②.测试
重新部署,运行,弹出的页面输入http://localhost:8080/day3/demo1,回车
页面地址还是http://localhost:8080/day3/demo1,并显示 显示结果的页面
3.二者区别
转发(较常用 效率更高) | 重定向 |
---|---|
浏览器URL不变 | 浏览器URL的地址栏改变 |
服务器行为(服务器内部) | 客户端行为(告诉客户端重新请求) |
浏览器只做了一次访问请求 | 浏览器做了至少两次的访问请求的 |
2次跳转之间传输的信息不会丢失 | 2次跳转之间传输的信息会丢失(request范围) |
重定向过程:浏览器第一次发起请求,servlet接收到,第一次响应发回给浏览器,并给浏览器发一个重新发起请求的地址。浏览器拿到响应后会根据新的地址,马上发一个新的请求。再由新的处理单元(这里是jsp)将响应返回给servlet。
请求转发过程:浏览器发起请求,服务器的一个处理单位接收到后,并没有响应,而是将请求转发给服务器的另一个处理单元,新的处理单元做出响应,返回给浏览器。
(req.getRequestDispatcher("pages/showRes.jsp").forward(req,resp);两个处理单元共享了请求和响应)
4.使用场景
重定向会告知浏览器第二次要访问的地址在哪,且2次跳转的数据可能会丢失。故两个处理单元没有直接数据关联时使用重定向。
请求转发,第一个处理单元处理出的数据要给第二个处理单元用使用,可用请求转发,此时要共享的数据放到req对象里。
上面的例子,两个处理单元没有关联,使用重定向和请求转发都可以。
下面看一个两个处理单元关联的例子:
(实际就是将以前一个servlet里的事情拆成两个去写)
①.将lombok.jar引入
javasm包下新建entity包,新建Users类
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class Users { private Integer userId; private String userName; private String userPwd; private String userPhone; public Users(String userName, String userPwd) { this.userName = userName; this.userPwd = userPwd; } }
②.第一个处理单元 MyServlet类
@WebServlet("/demo1") public class MyServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("业务逻辑走完了"); List<Users> listUser = new ArrayList<>(); listUser.add(new Users(1,"jack","abc123","123321233")); listUser.add(new Users(2,"jack2","abc123","123321233")); listUser.add(new Users(3,"jack3","abc123","123321233")); req.setAttribute("listUser",listUser); //将需要共享的数据封装到req对象里 req.getRequestDispatcher("pages/showRes.jsp").forward(req,resp); //将req和resp对象都传到第二个处理单元 } }
③.第二个处理单元 showRes.jsp
<html> <head> <title>Title</title> </head> <body> 显示结果的页面 <%--从reques域取值,并转回实际类型--%> <% List<Users> myusers = (List<Users>) request.getAttribute("listUser"); %> <table border="1"> <tr><th>编号</th><th>姓名</th><th>密码</th><th>电话</th></tr> <tbody> <% for(Users users:myusers){ %> <tr> <td><% out.print(users.getUserId()); %> </td> <td><% out.print(users.getUserName()); %> </td> <td><% out.print(users.getUserPwd()); %> </td> <td><% out.print(users.getUserPhone()); %> </td> </tr> <% }%> </tbody> </table> </body> </html>
④.测试
访问:http://localhost:8080/day3/demo1,回车
出现:
The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
路径出错,检查一遍,修改后再次运行(代码修改后就得重新部署,再运行,要不Tomcat不知道)
访问:http://localhost:8080/day3/demo1,回车
结果,网址还是http://localhost:8080/day3/demo1,页面显示:
显示结果的页面
编号 | 姓名 | 密码 | 电话 |
---|---|---|---|
1 | jack | abc123 | 123321233 |
2 | jack2 | abc123 | 123321233 |
3 | jack3 | abc123 | 123321233 |
一个细节问题:
jsp作为处理单元可以独立访问,在网页输入http://localhost:8080/day3/pages/showRes.jsp,结果报错,显示空指针。页面上只要看到500,就是代码有错,回到idea,有三个控制台,找报空指针异常的控制台。
控制台还会将出现错误的代码和其前后代码都展示出来,第29行是出错的代码。
null的对象调用属性或者方法会报空指针,这一行的myusers可能会空,代码开头
<% List<Users> myusers = (List<Users>) request.getAttribute("listUser"); %>
如果直接访问http://localhost:8080/day3/pages/showRes.jsp,myusers确实是空的。
为了使这些处理单元能正常访问,一般会加一个非空判断:
<body> 显示结果的页面 <%--从reques域取值,并转回实际类型--%> <% List<Users> myusers = (List<Users>) request.getAttribute("listUser"); %> <table border="1"> <tr><th>编号</th><th>姓名</th><th>密码</th><th>电话</th></tr> <tbody> <% if (myusers!=null){ for(Users users:myusers){ %> <tr> <td><% out.print(users.getUserId()); %> </td> <td><% out.print(users.getUserName()); %> </td> <td><% out.print(users.getUserPwd()); %> </td> <td><% out.print(users.getUserPhone()); %> </td> </tr> <% } } %> </tbody> </table> </body>
(这也是空指针异常的解决流程)
5.EL表达式
5.1 El表达式作用
1.EL即Expression Language(表达式语言)
2.页面中嵌入java代码的写法容易造成结构混乱,不易阅读和调试,可以使用一些其他技术来提高可阅读性和对原始使用方式有一定优化,如el表达式和jstl标签。
3.El表达式的主要作用为从域对象或参数中取值并输出到页面中。
5.2 El表达式特点
1.自动转换类型
EL得到某个数据时可以自动转换类型
对于类型的限制更加宽松
2.使用简单
相比较在JSP中嵌入Java代码,EL应用更简单
3.EL的语法
以"${"作为开始,以"}"作为结束
5.3El表达式操作
1.el表达式 核心功能 从域中取值 并输出到页面中
①.pages包下新建1ELDemo.jsp
<html> <head> <title>Title</title> </head> <body> <% request.setAttribute("mykey","myval"); %> ${mykey} </body> </html>
②.测试
重写部署,运行,网页输入http://localhost:8080/day3/pages/1ELDemo.jsp
网页直接显示myval
若body标签里只有
${mykey}
运行后,也不会报错,只是页面为空,没有显示。这就是el自动的非空判断功能。
若value部分由字符串改为对象
<body> <% request.setAttribute("mykey",new Users(1,"jack","123","123")); %> ${mykey} </body>
运行后,页面显示:Users(userId=1, userName=jack, userPwd=123, userPhone=123)
故el可自动类型转换
(导致代码看起来很乱的原因也正是一堆非空判断和类型强转)
操作符“.” 获取对象的属性
<body> <% request.setAttribute("mykey",new Users(1,"jack","123","123")); %> ${mykey.userName} </body>
会输出:jack
操作符“[]” 获取集合中的对象,例如:${newsList[0]}
2.el表达式中 可以使用运算符
<body> ${empty mykey} </body>
该句的意思是mykey是空。返回的是boolean值。这里确实没有
访问http://localhost:8080/day3/pages/1ELDemo.jsp,网页显示true
<body> ${not empty mykey} </body>
可加not。
5.4El表达式隐式对象
EL表达式包含一些隐式对象
1.作用域访问对象
可用来指定从某个域中取值 默认从上到下取值
pageScope
requestScope
sessionScope
applicationScope
示例:
出现两个域,都有值。(域不同,存储空间不同,允许存在相同的key)
<body> <% request.setAttribute("mykey",new Users(1,"jack","123","123")); %> <% session.setAttribute("mykey","sessionval"); %> ${mykey} </body>
结果是对象
想要访问session域的key
<body> <% request.setAttribute("mykey",new Users(1,"jack","123","123")); %> <% session.setAttribute("mykey","sessionval"); %> ${sessionScope mykey} </body>
(大部分时候不需要指定,因为key一般不起重名)
2.参数访问对象
可指定从request的参数部分取值
param 单个的键值对的key
paramValues 同名的键值对的key(数组用下标去取)
示例:
<body> ${param.testkey} </body>
重新部署运行后,页面输入:http://localhost:8080/day3/pages/1ELDeml.jsp?testkey=abc123
页面显示abc123
页面输入:http://localhost:8080/day3/pages/1ELDeml.jsp?testkey=
页面不显示(也做了非空判断)
页面输入:http://localhost:8080/day3/pages/1ELDeml.jsp?
页面不显示(也做了非空判断)
3.Jsp配置对象
pageContext 页面里的综合对象,很多内容可通过它获取
显示相对根路径的入口部分
<body> ${pageContext.request.contextPath} </body>
结果:/day3
然后就可以拼一些路径,比如a标签
<body> <a href="${pageContext.request.contextPath}/index.jsp">去主页</a> </body>
注:el是官方提供的jsp写java代码的标准的基础上,对java代码又进行一次翻译,也需要jar包支持。只不过该jar包不用自己加,在Tomcat安装目录下的lib目录里有el-api.jar(Tomcat已默认将该jar包加到自己的运行jar包里)
6.JSTL标签
el表达式主要功能就是取值和输出,写不了复杂的功能。
6.1 Jstl标签作用
1.El表达式主要是取值和输出功能,需要配合jstl标签来完成逻辑控制(条件判断、循环)
2.JSTL(JavaServerPages Standard Tag Library) jsp基本标签库,主要用来替换页面中跟逻辑判断相关的java代码,经常和el表达式配合使用
6.2Jstl使用
Jstl需要jar包支持
Index of /dist/jakarta/taglibs/standard/binaries
下载jstl.jar和standard.jar包
在使用jstl的页面中,需要加入taglib指令,导入核心标签库
<%@ taglib uri="Oracle Java Technologies | Oracle" prefix="c" %>
①.下载好jar包(jstl.jar 和 standard.jar)
②.在web.WEB-INF下新建lib目录
将jar包粘贴进去
③.给当前项目配置
Project Structure---->Modules------>右边的加号------->找到②中放jar包的位置,选中两个jar包,应用,确定。
④.新建web.pages.2JSTLDemo.jsp
在页面加入taglib指令(tag标签,tagName标签名,taglib标签库。)。jsp里官方留了一个入口,允许引入第三方的工具,比如标签库。jstl就是这样一套标签库。引入不同的标签库就是使用taglib指令。
<%@ taglib uri="Oracle Java Technologies | Oracle" prefix="c" %> 该路径实际是从引入的jar包找标签库,使用的是uri。 prefix是前缀的意思,引入之后,使用的以c:打头的标签,都是从引入的jstl找到的。c是core的缩写。不用缩写,写成core,那就以core:打头。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>Title</title> </head> <body> </body> </html>
6.3Jstl中常用标签
编写用标签写,然后自动转成java代码。故,都是<C: 标签名 属性=属性值>形式
1.通用标签
set:设置指定范围内的变量值(指定域里设置键值对)
var属性设置key,value属性设置value,scope属性设置域。
<body> <c:set var="mykey" value="myval" scope="session" ></c:set> ${sessionScope.mykey} </body>
访问:http://localhost:8080/day3/pages/2JSTLDemo.jsp
结果:myval
out:计算表达式并将结果输出显示
<body> <c:out value="123"></c:out> </body>
访问:http://localhost:8080/day3/pages/2JSTLDemo.jsp
结果:123
(el就能输出,故一般不用这个标签)
remove:删除指定范围内的变量(从指定域去除键值对)
<body> <c:set var="mykey" value="myval" scope="session" ></c:set> ${sessionScope.mykey} <c:remove var="mykey" scope="session"></c:remove> 删除后的结果: ${sessionScope.mykey} </body>
访问:http://localhost:8080/day3/pages/2JSTLDemo.jsp
结果: myval 删除后的结果:
2.条件标签
If:条件判断语句
必须的属性test,是判断的条件
<body> <c:set var="mykey" value="myval" scope="session" ></c:set> ${sessionScope.mykey} <c:if test="$(not empty mykey)"> 欢迎你 登陆成功 </c:if> </body>
访问:http://localhost:8080/day3/pages/2JSTLDemo.jsp
结果: myval 欢迎你 登陆成功
如果登录失败,让页面跳转:
<body> <c:if test="$(empty mykey)"> <c:redirect url="/index.jsp" /> </c:if> </body>
(底层做的重定向)
访问:http://localhost:8080/day3/pages/2JSTLDemo.jsp
结果:跳转到http://localhost:8080/day3/index.jsp
3.迭代标签
forEach:遍历集合中的元素时使用
关键属性items,传入要遍历的集合。属性var,每次遍历到一条属性后,需要有一个暂停,会自动暂停到page域里,取值也是从page域去取。var的属性值key由自己定义。
其他的属性不是必须的:begin从第几条开始 end从第几条结束 step每次查看几条 varStatus创建变量的状态
示例:
在MyServlet类中,修改转发路径:
@WebServlet("/demo1") public class MyServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("业务逻辑走完了"); List<Users> listUser = new ArrayList<>(); listUser.add(new Users(1,"jack","abc123","123321233")); listUser.add(new Users(2,"jack2","abc123","123321233")); listUser.add(new Users(3,"jack3","abc123","123321233")); req.setAttribute("listUser",listUser); //将需要共享的数据封装到req对象里 req.getRequestDispatcher("pages/2JSTLDemo.jsp").forward(req,resp); //将req和resp对象都传到第二个处理单元 } }
在2JSTLDemo.jsp中:
<body> <table border="1"> <tr><th>编号</th><th>姓名</th><th>密码</th><th>电话</th></tr> <tbody> <c:forEach items="${listUser}" var="myuser"> <tr> <td>${myuser.userId}</td> <td>${myuser.userName}</td> <td>${myuser.userPwd}</td> <td>${myuser.userPhone}</td> </tr> </c:forEach> </tbody> </table> </body>
访问:http://localhost:8080/day3/demo1
结果:
编号 | 姓名 | 密码 | 电话 |
---|---|---|---|
1 | jack | abc123 | 123321233 |
2 | jack2 | abc123 | 123321233 |
3 | jack3 | abc123 | 123321233 |
7.改造servlet的访问控制案例
①.src.com.javasm.controller2.LoginServlet类:
根据业务执行结果,决定展示的页面数据。
如果用户不为空,表示登录成功,此时有两个事要做:1.往session里放一个登录的用户;2.跳转到主页面。之前是自己输出一些内容作为主页面,现在专门写一个主页面让它去调。
如果用户为空,说明用户名或者密码错误,此时跳回到登录页面。但登录页面需要有提示信息,告诉用户,用户名或者密码错误。数据间有关联,做好用转发的跳转方式。要转发的数据用req对象去传,用req.setAttribute放东西。
@WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); Users inserUser = new Users(username,password); //封装成对象使用 //调用业务逻辑 LoginService ls = new LoginServiceImpl(); Users user = ls.getUser(inserUser); //根据业务执行结果 决定展示的页面数据 if(user!=null){ HttpSession session = req.getSession(); session.setAttribute("loginUser",user); //跳转到主页面 }else{ //用户名或密码错误 //跳回登录页面 //有数据在页面显示 用户名或密码错误 req.setAttribute("loginMSG","用户名或密码错误"); req.getRequestDispatcher("/loginPage.jsp").forward(req,resp); } } }
②.web.loginPage.jsp
有两种状态:1.单独访问的状态;2.登陆失败跳过来的状态
跳转过来,且传过来数据,则状态2,不传过来东西,则状态1。用el表达式去做,有非空判断,不会报错。
只需在最后加:${loginMSG}
<span class="alertText">${loginMSG}</span>
如果有值,最后将值打出。没值,就是正常的登录页面。
完整的代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <meta charset="UTF-8"> <title>loginPage</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/bootstrap.css"/> <script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="${pageContext.request.contextPath}/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> <style> .mycls{ width: 600px; height: 400px; border: 1px solid gray; border-radius: 10px; margin: 200px auto; padding: 50px; } .alertText{ margin-left: 10px; color: lightcoral; font-size: small; } </style> </head> <body> <div class="mycls"> <h1>登录</h1> <hr/> <form action="${pageContext.request.contextPath}/login" method="post" class="form-horizontal"> <div class="form-group"> <label for="username" class="col-sm-2 control-label">用户名</label> <div class="col-sm-10"> <input type="text" class="form-control" id="username" name="username" placeholder="username"> </div> </div> <div class="form-group"> <label for="password" class="col-sm-2 control-label">密码</label> <div class="col-sm-10"> <input type="password" class="form-control" id="password" name="password" placeholder="Password"> </div> </div> <hr/> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">登录</button><span class="alertText">${loginMSG}</span> </div> </div> </form> </div> </body> </html>
③.登录失败会返回登录页面,并告知用户名或者密码错误。但用户并不知道自己哪里错了,用户体验度不高,应该将用户之前输入的东西再写回来。
在LoginServlet类中,当用户为空时,在转发时,除了传递用户名或者密码错误的信息,还可以将错误的用户名和密码也传过去。方便loginPage.jsp将错误的用户名和密码写上去,给用户看。
else{ //用户名或密码错误 //跳回登录页面 //有数据在页面显示 用户名或密码错误 req.setAttribute("loginMSG","用户名或密码错误"); //将错误的用户名和密码也传过去 req.setAttribute("inserUser",inserUser); req.getRequestDispatcher("/loginPage.jsp").forward(req,resp); }
给文本框加初始值用value属性。
<input type="text" class="form-control" id="username" name="username" value="${inserUser.userName}" placeholder="username">
<input type="password" class="form-control" id="password" name="password" value="${inserUser.userPwd}" placeholder="Password">
④.现在写登陆成功后的主页面
新建web.pages.main.jsp
主要展示:欢迎xxx回来
请使用功能
用户列表 订单列表
<html> <head> <title>loginPage</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/bootstrap.css"/> <script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="${pageContext.request.contextPath}/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> </head> <body> 欢迎 ${loginUser.userName} 回来<br/> 请使用功能: <input type="button" myaddr="/user" class="btn btn-default myBtn" value="用户列表"> <input type="button" myaddr="/order" class="btn btn-default myBtn" value="订单列表"> <hr/> </body> <script> $(".myBtn").click(function(){ location.href = $(this).attr("myaddr"); }) </script> </html>
⑤.在LoginServlet类增加用户不为空时的跳转方式:
主页面和servlet没关联,用重定向:
if(user!=null){ HttpSession session = req.getSession(); session.setAttribute("loginUser",user); //跳转到主页面 resp.sendRedirect(req.getContextPath()+"/pages/main.jsp"); }
⑥.main.jsp的跳转地址之前写了后半段,前半段是项目名,用el去写
<script> $(".myBtn").click(function(){ location.href = ${pageContext.request.contextPath} $(this).attr("myaddr"); }) </script>
${pageContext.request.contextPath}是el表达式
$(this)是jquery代码,会不会有冲突。
el表达式实际上是java代码,jquery实际上是js代码。java代码在服务器运行,js代码在用户浏览器运行。当js代码运行时,java代码已经运行完了(已经输出成对应标签让浏览器执行了)。
运行的位置不同,el表达式先与jquery代码运行,el用于拼代码和标签。
所以这样拼是有问题的。
在js里引号里的才是字符串
正确的写法:
<script> $(".myBtn").click(function(){ location.href = "${pageContext.request.contextPath}"+ $(this).attr("myaddr"); }) </script>
防止出错,也可写在前面:
<body> 欢迎 ${loginUser.userName} 回来<br/> 请使用功能: <input type="button" myaddr="${pageContext.request.contextPath}/user" class="btn btn-default myBtn" value="用户列表"> <input type="button" myaddr="${pageContext.request.contextPath}/order" class="btn btn-default myBtn" value="订单列表"> <hr/> </body> <script> $(".myBtn").click(function(){ location.href = $(this).attr("myaddr"); }) </script>
⑦.假如跳转到用户列表,之前是查数据并展示用户列表。
现在查到所有用户信息后,将它放到req里,找一个页面去做显示。
@WebServlet("/user") public class UserServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { UserService us = new UserServiceImpl(); List<Users> allUsers = us.getAllUsers(); req.setAttribute("listUser",allUsers); req.getRequestDispatcher("/pages/showAllUsers.jsp").forward(req,resp); } }
注:放数据时,可用request,session,application放,原则是能往小了放就往小了放。
⑧.对应的展示页面
新建web.pages.showAllUsers.jsp
<html> <head> <title>Title</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/bootstrap.css"/> <script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="${pageContext.request.contextPath}/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> </head> <body> <table class="table"> <tr> <th>编号</th> <th>姓名</th> <th>密码</th> <th>电话</th> </tr> <tbody> <c:forEach items="${listUser}" var="myuser" > <tr> <td>${myuser.userId}</td> <td>${myuser.userName}</td> <td>${myuser.userPwd}</td> <td>${myuser.userPhone}</td </tr> </c:forEach> </tbody> </body> </html>
⑨.做访问控制
之前的代码只控制了servlet,但jsp没有控制,仍可直接访问。
想要都控制起来,怎么做?
UserServlet看看有没有LoginServlet的key,有key,执行代码,没有则跳回登录页面。
对于showAllUsers.jsp,
用<c:if test="${empty mykey}">
<c:redirect url="/index.jsp" /> 的形式。如果指定的key不存在,跳转。
实际上,UserServlet可偷懒,UserServlet的代码里,它跳转到的是showAllUsers.jsp,可最后统一在showAllUsers.jsp控制。
<body> <c:if test="${empty loginUser}"> <c:redirect url="/loginPage.jsp"></c:redirect> </c:if> <table class="table"> <tr> <th>编号</th> <th>姓名</th> <th>密码</th> <th>电话</th> </tr> <tbody> <c:forEach items="${listUser}" var="myuser" > <tr> <td>${myuser.userId}</td> <td>${myuser.userName}</td> <td>${myuser.userPwd}</td> <td>${myuser.userPhone}</td </tr> </c:forEach> </tbody> </body>
⑩.只要需要做访问控制的都要做⑨的过程。比如上面的main.jsp
~~~java
<c:if test="${empty loginUser}"> <c:redirect url="/loginPage.jsp">/c:redirect /c:if ~
故此段代码是公共代码,可用include指令,把其他页面引入进来。
可先专门做一个公共页面,新建web.pages.commom.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <c:if test="${empty loginUser}"> <c:redirect url="/loginPage.jsp"></c:redirect> </c:if>
之后,在showAllUsers.jsp和main.jsp的body标签下的第一行代码加入如下代码:
<%@include file="common.jsp" %>
⑪.测试