大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: 从JSP到Spring 修改讲义

JSP本质上就是一个HTML内容和Java代码的混合,学习JSP就是学习如何在HTML中嵌入Java可编程代码。


变量和表达式

JSP的标志符号就是:<% %>。其中如果:(以上写法注意%后空格和末尾的分号)

  • 以感叹号开头,就是用于变量声明的:
    <%! String name = "大飞哥"; %>
  • 以等号开头,被称之为表达式,可用于变量值输出:
    <h1>源栈欢迎你 <%= name %> </h1>
    或者表达式运算结果的输出
    <h1>源栈欢迎你, <%= name+"(飞哥)" %></h1>
  • 什么符号都没有,就是直接执行的代码块
    <% name += ",皮"; %>
    代码块中的代码,可以一行,也可以多行。你在Java中怎么写的,这里就可以怎么写(变量赋值,分支循环……)
    <% 
    	String name = "fg";	
    	name+="(飞哥)";	
    	if(LocalDate.now().isAfter(LocalDate.of(2021, 11, 19))){}
    %>
    在这种代码块中,有一种特殊的方法,执行后会输出字符串到HTML页面中:
    <% out.print(name); %>

    以上是存文本输出,如果要有HTML标签的话(不建议):

    out.print("<p>" + name + "</p>");

    注意:前面没有System.前缀,因为这不是控制台输出!而是JspWriter


注释

代码块里面的注释,和普通Java代码一样

<% 
	String name = "fg";	
	/*name+="(飞哥)";*/	
	out.print(name);
	//System.out 
%>

代码块外面的注释,使用:<%--  --%>

<%-- <%!String name = "大飞哥";%> --%>
<%-- <%= name %> --%>

注意和HTML注释的区别:

  • JSP:注释掉的内容根本不会传到客户端
  • HTML:会传到客户端,只是浏览器不显示(演示)
    <!-- 源栈欢迎你 --> 


分支循环

在JSP的代码块中,不能出现HTML标签。所以这样的写法是无法编译通过的:

<% boolean smart = true; %>
<%
	if(smart){
		<h1>飞哥帅呆了</h1>
	}
%>
要么我们使用out.print():
out.print("<h1>飞哥帅呆了</h1>");

要么我们就需要将非HTML内容用<%%>分别包裹起来:

<% if(smart) {%>
	<h1>飞哥帅呆了</h1>
<%} %>

else也一样:

<% if(smart) {%>
	<h1>飞哥帅呆了</h1>
<%} else { %>
	<h1>飞哥自恋狂</h1>
<%} %>

一样可以分支嵌套,switch也以此类推,略。

for循环:

<% for(int i = 0; i< 5; i++) { %>	
	<p><%=i%></p>
<% } %>

while循环:

<%! int i = 0; %>
<% while(i<5){ %>
	<p><%=i%></p>
	<% i++; %>	
<% } %>

演示举例说明:一起帮哪些页面

  • 需要分支(导航栏),
  • 需要循环(关键字),
  • 还需要分支循环嵌套使用(求助列表)


指令

一个JSP页面可以包含多个指令,指令以<%@ %>标记。常用的有:

page指令

@后接page,再后面接键值对:

  • 指示页面的语言、内容类型、编码格式等
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8"%>
  • 引入java包
    <%@ page import="java.time.DayOfWeek"%>

page指令可以自由拆分/合并(演示,略)

include指令

引入其他页面(.jsp或.html)

在@后接include,然后由file指定页面路径:页面可以是jsp,也可以是html;路径可以是相对的,也可以是绝对的。

<%@ include file="shared/_navigator.jsp" %>
<%@ include file="/_footer.html" %>

使用include指令就可以让我们把多个页面共用的部分(比如页头/页脚)抽出来,供其他页面反复重用,告别静态页面大量的Ctrl+C和Ctrl+V。


内置对象

JSP为我们提供了很多直接就可以使用的对象。比如封装了HTTP请求的

request

利用request对象,可以获得基于url的:
<!-- URL包含了:协议、域名和端口 -->
request.getRequestURL(): <%= request.getRequestURL() %> <br />
<!-- URI仅有端口之后的path部分 -->
request.getRequestURI(): <%= request.getRequestURI() %> <br />
<!-- ServletPath去掉了Servlet名 -->
request.getServletPath():<%= request.getServletPath() %><br />

<!-- 获取协议、域名和端口 -->
request.getServerName():<%= request.getServerName() %><br />
request.getProtocol():<%= request.getProtocol() %><br />
request.getServerPort():<%= request.getServerPort() %><br />

GET请求的url参数(query string),或者POST请求的form data

	<%= request.getQueryString() %>	<br />
	<%= request.getParameter("n") %><br />
请求的method(GET还是POST等):
	<%= request.getMethod() %> <br />

以及客户端的一些信息:

<!-- 客户端信息,相对于服务器就是remote -->
request.getRemoteAddr():<%= request.getRemoteAddr() %>
request.getRemotePort():<%= request.getRemotePort() %>
其他所有的信息都还可以通过header获取:比如referer(上一页)
<%= request.getHeader("referer")%> <br />

演示:在另一个页面:

<a href="Welcome.jsp">welcome</a>

最后,来个大杀技!

<%
	Enumeration<String> names = request.getHeaderNames();
	while(names.hasMoreElements()){
		String name = names.nextElement();
		out.println( name  + "    :    " + request.getHeader(name) + "<br />");
	}
%>

可以看出,cookie是可以从request中获取的(由response生成)

此外,还有response、session、pageContext、application、exception等内置对象,完全可以满足web开发所需……


页面跳转

实际开发中经常会出现页面自动跳转的场景,比如:未登录用户访问一个需要登录才能访问的页面,17bang会自动转到登录页面。

JSP中有两种方式可以实现这种需求:

  • forward跳转,这种以<jsp:开头的标记被称之为JSP动作,除了forward还有include、useBean等……
    <jsp:forward page="/logon.jsp"></jsp:forward>
  • redirect重定向
    <% response.sendRedirect("logon.jsp"); %>

#常见面试题# forward和redirect的区别:(复习:状态码301和302


forward
redirect
需要客户端参与
否 

客户端发起请求次数
1
2
浏览器地址栏显示
最初请求的地址
跳转过后的地址

context path

注意:foward和sendRedirect()使用的路径格式一样:

  • 在jsp:forward的page值中,斜杠开头代表webapp文件夹;
  • 在sendRedirect()中,斜杠开头代表URL根目录。但是,由于Servlet对应的ContextPath存在,域名/logon.jsp是无法访问的(演示)
<%= request.getContextPath() %>

ContextPath用于确定servlet(后文详述)。

所以我们这里使用相对路径,一定要使用绝对路径的话,需要拼接context path:

<% response.sendRedirect(request.getContextPath() + "/logon.jsp"); %> 


useBean

在JSP中可以引入一个(或多个)Java类。按惯例,这种类通过getter/setter暴露数据,又被称之为JavaBean。

我们可以把大量的复杂逻辑放到JavaBean中,这样页面代码就可以变得更加的简洁清爽(复习:PageModel模式)。这是JSP一样能开发大型项目的底气!

使用JSP动作useBean引入,class指定类名,id为当前页使用的“别名”(或者该类的变量名),在当前页使用:

<jsp:useBean id="author" class="bean.jsp.bang.User"></jsp:useBean>
可以用JSP动作读/写Bean的属性
<jsp:setProperty property="name" name="author" value="飞哥"/>
<jsp:getProperty property="name" name="author"/>
还可以直接url参数获值赋予给JavaBean的属性,比如:/Welcome.jsp?n=fg
<jsp:setProperty property="name" name="author" param="n"/>

如果不断的完善规范Bean,就会形成PageModel的框架

  • 取值显示到页面:
    public void init() {
    	this.name = "飞哥";    //取自数据库
    }
    <% author.init(); %>
  • 页面获值到后台:
    public void save() {
    	//模拟保存到数据库
    	System.out.println(this.name + "has been saved");
    }
    <jsp:setProperty property="name" name="author" param="n"/>
    <% author.save(); %>


底层原理

对外tomcat作为服务器响应所有HTTP请求,但是对内tomcat本身只是一个容器,它的功能实现依赖于其内部的servlet。

servlet

servlet是继承自javax.servlet.http.HttpServlet的类,比如tomcat就默认带有两个servlet:

  • org.apache.catalina.servlets.DefaultServlet
    public class org.apache.catalina.servlets.DefaultServlet 
            extends javax.servlet.http.HttpServlet {
  • org.apache.jasper.servlet.JspServlet
    public class org.apache.jasper.servlet.JspServlet 
           extends javax.servlet.http.HttpServlet

web.xml

tomcat中有哪些servlet,servlet间如何分工,都是由web.xml配置的。

tomcat自带一个默认的web.xml:(我们也可以在项目中添加自定义的web.xml,自定义的web.xml拥有更高的优先级,SpringMVC中使用)

其中就可以看到已经配置了

  • servlet节点
        <servlet>
            <servlet-name>jsp</servlet-name>
            <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
  • servlet-mappin节点,注意url-pattern节点,指示该servlet处理哪种HTTP请求
        <servlet-mapping>
            <servlet-name>default</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    不需要特殊处理的所有基本的HTTP请求,如html页面、图像、.js/.css文件等静态资源
        <servlet-mapping>
            <servlet-name>jsp</servlet-name>
            <url-pattern>*.jsp</url-pattern>
            <url-pattern>*.jspx</url-pattern>
        </servlet-mapping>

    即:所有.jsp结尾的文件请求由jsp servlet负责

HttpJspBase

servlet会将JSP页面解析成一个java类

演示:在eclipse中双击server查看tomcat的工作路径

-Dcatalina.base="D:\Users\86186\eclipse-workspace-2021-09\.metadata\.plugins\org.eclipse.wst.server.core\tmp1" 说明:eclipse中的tomcat是被当做插件使用的,上述目录被用于存放tomcat工作时所需文件资料。

其下\work\Catalina\localhost\jsp.17bang\org\apache\jsp中存放着.jsp对应的java类(源文件+编译后文件)

所有类都继承自HttpJspBase:
public final class Welcome_jsp extends org.apache.jasper.runtime.HttpJspBase
其中最核心的方法就是:
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
里面大量的调用out.write()方法用于向客户端输出内容:
      out.write("<!DOCTYPE html>\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<meta charset=\"UTF-8\">\r\n");

演示:观察本章节JSP内容(变量赋值/分支循环/指令动作等)如何生成相应的Java代码。

理解:

  • 代码块中声明的变量和<%! %>声明的变量,一个是方法本地变量,一个是类的字段,所以有不同的作用域
  • <%= %>表达式内容被简单out.print()
  • <% %>代码块中的代码被原封不动的复制
  • <jsp:useBean>会new出一个对象
  • ……


#常见面试题:<%@include> 和 <jsp:include>的区别?#

这个问题看生成的HttpJspBase代码最直观:

  • <%@include> 是将被include页的最终效果(out.print())引入,可以直接输出:
    out.write("<p>导航栏</p>\r\n");
    out.write("<a href=\"../Welcome.jsp\">welcome</a>");
  • <jsp:include>是调用方法引入页面,还需要再进一步执行:
    org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "shared/_navigator.jsp", out, false);
所以,才有这样的表述:

区别

@include

<jsp:include

执行时间

翻译阶段

在请求处理阶段

引入的内容

所生成的应答文本 一个include()方法


JDNI

当变成Web项目之后,因为整个项目运行在tomcat这个容器中,就不能再像控制台一样直接JDBC了。

这时候我们需要在tomcat中注册

首先在context.xml中添加:

<Resource name="jndi/mysql17bang" auth="Container"
	type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver"
	url="jdbc:mysql://localhost:3306/17bang" username="root"
	password="" maxActive="20" maxIdle="10" maxWait="10000" />
然后在web.xml引入:
<resource-ref>
	<description>JNDI DataSource</description>
	<res-ref-name>jndi/mysql17bang</res-ref-name>
	<res-type>javax.sql.DataSource</res-type>
	<res-auth>Container</res-auth>
</resource-ref>
最后Java代码中:
Context ctx = new InitialContext();
//注意这个前缀:java:comp/env/
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jndi/mysql17bang");
Connection conn = ds.getConnection();
System.out.println(conn.isClosed());
注意选择正确的import:
import javax.naming.*;



作业

  1. 利用循环分支,实现如下图所示的页面效果:

    简便方式:使用CSS定位而不使用空格
  2. 参考一起帮·任务历史月表新建 /Task/History/Month 页面,利用表格,输出按月按周组织的任意一个月份的日历。
  3. 在shared目录下放置抽出的_header.jsp、_footer.jsp、_side.jsp等公用部分页,利用include组装以下涉及页面
  4. 做一个分页的部分页_pager.jsp,里面并排10个页码链接,其URL能:
    1. 根据当前页面的路径相应的变化
    2. 包含url参数pageIndex的值
    比如_pager.jsp被include到
    • /article/index.jsp中,链接的url就是:/article/index.jsp?pageIndex=1、/article/index.jsp?pageIndex=2、……
    • /problem/index.jsp中,链接的url就是:/problem/index.jsp?pageIndex=1、/problem/index.jsp?pageIndex=2、……
    最后还可以再加上一页和下一页的链接
  5. 地址栏输入:/article/single.jsp?id=5,能通过查询数据库,
    1. 输出id=5的article的标题、正文和关键字
    2. 如果id值在数据库中找不到,forward到错误页(/error.jsp),提示:没有这篇文章
  6. /register.jsp:注册页面,用户输入用户名和密码,提交之后页面刷新:
    1. 提示用户名重复,或者
    2. 用户名和密码存入数据库,显示注册成功
  7. /article/new.jsp需要登录(用url参数user=xxx模拟)才能访问,未登录用户(没有带url参数user)访问,会自动跳转到登录页面(/login.jsp)。如果用户
    1. 登录成功,自动跳转到/article/new.jsp(user参数值为用户id)
    2. 错误,页面刷新,提示:用户名或密码错误
说明:本次作业涉及到数据库操作的,都在JavaBean中用JDBC完成
学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 从JSP到Spring 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码