JSP技术

JSP(全称JavaServer Pages)是由 Sun Microsystems 公司倡导和许多公司参与共同建立的一种使软件开发者可以响应客户端请求,而动态生成HTML、XML或其他格式文档的Web网页的技术标准。
最终还是要由 JSP 编译器翻译成 Servlet 执行

JSP运行原理

服务器会先将 JSP 翻译为 Servlet ,最后会生成在 Tomcat 的 Work 文件夹,可以去扒一下源码,其实就是 Servlet

访问一个网站的流程一般是:浏览器访问 –> Servlet —(通过转发)–> JSP
注意,转发使用的是一个请求
如果有参数,使用 request 域将参数带过去

通过看 JSP 翻译后的 Servlet 文件,就可以发现 JSP 中的内容被翻译到了方法中,在这个方法中默认就定义了九大对象,我们称为是九大隐式对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {

final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;


try {
response.setContentType("text/html;charset=UTF-8");
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;

out.write('\r');
out.write('\n');

String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";

out.write("\r\n");
out.write("\r\n");
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("<base href=\"");
out.print(basePath);
........

我们看到,jsp 页面的内容其实就是用 out 进行了输出,如果嵌入了 java 代码(<% code %>),就会原封不动的翻译到这个方法里,需要注意一点是,可以看到上面翻译后的 JSP 默认调用了 getSession() 方法,也就是默认会给每一个会话产生一个 Session ,这个对于传值来说还是很方便的(比如常用的: request.getSession().setAttribute()),但是相应的也会耗费一些资源,毕竟每开一个窗口就会创建一个 Session,并且还是存在内存里
在 JSP 页面可以通过 session 属性来控制是否默认产生 Session

1
<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %>

总结一下,这九大对象是:

1
2
3
4
5
6
7
8
9
10
11
12
Request
Response
Session
Application
Config
Out * (有缓冲)
Exception
// 当前 JSP 的实例, object类型
// 它代表JSP被编译成Servlet,可以使用它来调用Servlet类中所定义的方法
// 注意 Page 和 pageContext 的区别
Page
pageContext *

其中有两个比较特殊的对象,已经使用 * 标出了

通过上面的代码我们知道 jsp 中的 html,默认也是用 out 输出,它是有缓冲区,当缓冲区满了或者页面关闭时才会刷到Response 去
所以,如果 代码中有 getWrite 的输出,那么html的内容会在它的后面,所以最好只使用 out 进行输出

当第一次访问 JSP 的时候会被翻译成 Servlet,而不是启动服务器的时候翻译,所以第一次访问 JSP 页面的时候比第二次慢得多,只要翻译过了,以后就会直接用


然后我们再来看看:pageContext
可以认为它是 JSP 的运行环境,它在内部封装了其他八大隐式对象的引用(多用于自定义标签)
所以,当需要传数据的时候可以直接传一个 pageContext 有了它,就等于啥也有了

同时它自身是一个域对象,可以用来保存数据,使用它的 setAttribute 方法

然后再来看看它的生命周期:就是 JSP 这个页面 打开/关闭,所以还是蛮短的

来看个非常好用的方法:findAttribute() 查找所有域中的数据,顺序为:
pageContext —> request —> session —> application 在 JSP 中,使用的 ${data} 其实就是调用的它
在 Jsp 中我们写链接的时候,当然不能把链接的路径给写死,一般都是使用 ${pageContext.request.contextPath} 获得当前 web 应用的路径,然后再补全具体的地址
还有两个也比较使用,省了不少事:forward(转发) 和 include 方法,不再多说


JSP页面中有一个内置的 exception 对象,这个 exception 对象是 Throwable 的实例。当在 JSP 中发生错误或异常时,就会将捕获的 java.lang.Throwable t 赋值给内置对象 exception 异常对象。
由此可见,exception 对象仅在异常处理页面中才有效,是异常处理页面!
整个 _jspService 方法会被 try 起来,当异常被捕获到后会交给 _jspx_page_context 处理,首先判断抛出异常的当前 JSP 页面 page 指令是否指定了 errorPage 属性,如果指定了,则将请求 forward 到 errorPage 属性指定的页面;否则就使用系统页面来输出异常信息。

1
2
3
4
5
6
7
8
9
<!-- page1 -->
<%@ page errorPage="/page2.jsp" %>
<%
int c = 1 / 0; // 抛出异常
%>

<!-- 下面的是 page2 -->
<!-- page2 中输出异常信息,因为都是 JSP 自带 exception 对象 -->
<%=exception.toString() %>

参考:http://www.jellythink.com/archives/1353

JSP指令/标签

首先要明确的是:JSP 是用于输出的,格式良好的JSP 不允许出现(尽量少的出现) Java 代码

专用的脚本表达式输出某个变量/属性:<%=name %> ;还看到有这样用的 <%=request.getContextPath()%> ,感觉还是用 $ 比较好

JSP 的声明一般有两种:

1
2
3
4
5
6
<%-- 这是注释,不会输出到 html 中 --%>

<%-- 下面标签中如果写java代码,会被翻译到 _jspService 方法中 --%>
<% code %>
<%-- 下面标签中如果写java代码,会被翻译到 _jspService 方法外中,所以可以定义方法 --%>
<%! code %>

JSP 指令是给解析引擎看的,这里也就是 Tomcat:

1
2
3
4
5
<%-- 比如导包、设定是否创建 session 对象、错误页面,但是不要太大,<1K --%>
<%@ page %>

<%-- 包含页面,是静态包含 --%>
<%@ include file="" %>

关于 include :
包含就是把整个页面搞进来,所以要 include 的页面最好不要写头标签 head 之类的,直接写重点即可
静态包含就是把这些页面拼合在一个 Servlet 中
而动态包含就是:翻译成多个 Servlet ,等访问时再合成,所以还是尽量使用静态的吧
例子:request.getRequestDispatcher("path").include(request,response);

下面来说说 JSP 的标签,主要的就是这三类:

  • <jsp:forward page="">
    就是转发啦,直接写地址就可以了
  • <jsp:param name="xx" value="dd">
    传递参数用的,常套在 forward 标签里使用
  • <jsp:include page="">
    就是动态包含了,不过不推荐

更多的标签可以去 Wiki 看下:https://zh.wikipedia.org/wiki/JSP 还是很详细的

EL表达式与JSTL

更新于:2017-4-21

EL 表达式

关于 EL 现简单说一个,以后再补充:
${data} —–> pageContext.findAttribute("data")
其实就是简化了代码,最大的一个好处是:如果没有找到就返回空字符串,不会影响显示


EL 表达式的作用嘛,基本上可以概况为四点:

  1. 获取数据
  2. 执行运算
  3. 获取 web 开发常用对象
  4. 调用 java 方法

获取数据

这个在之前写的就是了,就是分割线之前的内容,就是来获取数据的,其他的还有获取 Javabean 的,很简单,其实是获取的属性,只要有对应的 get/set 方法就行,写 EL 的时候省略 get;以及还有一些常用的:
${name} JSP 引擎会自动拿着 name 当key,去四个域查找,上面我们也说过了

1
2
3
4
5
// 获取 List 中的数据
// 和 JS 类似,data.key == data['key']
${data['1'].name}
// 获取 Map 中的数据
${data.key.name}

执行运算

语法:${运算表达式}
其实就是在 {} 内做一些逻辑判断之类的,用的比较多的是下面几个,注意是花括号不是小括号

1
2
3
4
<%-- 检测是否为 null 或者 空 --%>
${empty(data)}
<%-- 二元表达式的支持 --%>
<input type="radio" name="sex" ${user == 'nv'?'checked':''}>

获取 web 开发常用对象

这个用的比较频繁,前面其实也用到了,就是获取当前应用名的时候
EL 表达式定义了 11 种隐式对象
解析的时候先要判断传入的是否是隐式对象,语法 ${隐式对象名}

  1. pageContext
  2. pageScope
    返回的是 page 域中的 Map 集合;也就是从指定域 (page) 找
  3. requestScope
    返回的是 request 域中找 ,下同
  4. sessionScope
  5. applicationScope
  6. param
    返回请求参数的 Map 集合
  7. paramValues
    返回的是数组,对应多个请求参数的情况
  8. header
    返回的是请求头的 Map 集合
  9. headerValues
  10. cookie
    返回的是保存了 Cookie 的 Map 集合,
    注意:每一个 key 获取到的是一个 Cookie 对象
    比如这样用:${cookie.JSESSIONID.value/name}
  11. initParam
    返回的是web 应用的初始化参数的 Map 集合,
    就是 web.xml 文件中的 <content-param> 中的内容

执行 Java 代码

只能是 静态方法!
并且相应的方法需要在 tld 文件中进行描述
只能执行与 web 开发无关的代码,一些工具类啊,也能想得通,毕竟静态;所以它不能取代自定义标签
在 JSP 中的使用和自定义标签一样,tld 文件的定义名称、含包名的路径、方法的定义,类似:

1
2
3
4
5
<function>
<name>test</name>
<function-class>utils.WebUtils</function-class>
<function-signature>java.lang.String test(java.lang.String)</function-signature>
</function>

然后在 JSP 中这样用:${c:test("str")} ;c 是你导入时候定义的名称
不得不说 IDEA 的代码提示真是爽!

补充

其实还可以看出,EL 表达式是不支持字符串的连接的,还有一些其他需求的话自带的是满足不了我们的,所以就会有自定义 EL 函数了
一般习惯于命名为 MyEL
${} 中是可以套 el 函数的,比如下面的写法是完全可行的
${user == null?my:test(str):''}

还有一点需要注意的是:$ 中不能再嵌套 $ 写法上写一个就行了,效果还是有的

EL 表达式取出的类型会自动推断,不需要为类型(转换)而操心。

JSTL

至于 JSTL ,说白了就是用来做一些逻辑判断的,其实感觉就是一些自定义标签 ,毕竟它就叫 JSP 标准标签库嘛。
需要导入相应的 jar 包,jstl.jar 、 standerd.jar,放在 lib 目录下即可;
导入标签库:在 JSP 的开始声明:<%@ taglib url="http://java.sun.com/jsp/jstl/core" prifix="c" %>
url 可以在导入的 jar 的 c.tld 文件中找到,然后给定义个名称,一般使用 c ,和文件名统一(文件名就叫 c)。
类似这样的使用,temp 可以是个 bean:

1
2
3
4
5
6
7
8
<c:foreach var="temp" items="${list}">
${temp.name}
</c:foreach>

<c:if test="${name == null}" />

<!-- 字符串等比较除了 == 可以使用 eq -->
<c:if test="${name eq 'abc'}" />

核心标签库

  • out
    <c:out value='${data}' default='addd' escapeXml='true'></c:out>
    用来显示一个表达式的结果,与 <%= %> 作用相似
    escapeXml 控制是否进行转义,如果值为空就输出 default 的值

  • set
    <c:set var='key' value='data' scope='page'></c:set>
    可以设置 域、bean、map( key 为 property) 的数据; 比如上面的例子是存到 page 域

  • remove
    用来删除域中的数据,使用参考上面的 set

  • catch
    <c:catch var='key'></c:catch>
    用来处理产生错误的异常状况,并且将错误信息储存起来
    默认将异常存到 page 域,需要指定一个 key

  • if
    <c:if test='逻辑'/>

  • choose
    相当于 if…else,或者说 switch 分支语句,就是只选择一个,第一个通过后就不会向下执行了

    1
    2
    3
    4
    <c:choose>
    <c:when test=''>if true</c:when>
    <c:otherwise>else</c:otherwise>
    </c:choose>
  • forEach
    迭代,varStatus 对象表示当前选择的是那个; 还可以做分页,step 是步长

    1
    2
    3
    4
    5
    6
    7
    8
    <c:forEach var='key' varStatus='status' items="${list}" >
    <tr class="${status.conut%2==0?'even':'odd'}"></tr>
    </c:forEach>

    // 输出 1-7
    <c:forEach var='key' begin='1' end='7' step='1'>
    ${key}
    </c:forEach>
  • url
    主要用于 url 的重写! 会自动构建 url 的 Session 地址,自动加入当前应用的名字,不需要手动获取了,如果不写 var 属性就会默认输出到页面上,否则就存到 var 指定的变量中

    1
    2
    3
    4
    <c:url var='key' value='url'>
    // 设置 url 的 get 参数,如果是中文会自动进行编码
    <c:param name='key' value='测试'></c:param>
    </c:url>
  • redirect
    实现重定向

  • forTokens
    用 delims 定义的值来分割 items 里的数据,然后迭代
    <c:forTokens var='key' items='${data}' delims=','></c:forTokens>
    例如,items 中存的是字符串 “a,b,c,d” ;那么第一次迭代 key 就是 a,第二次是 b …..

JSTL 除了核心标签库还有其他的几个库,更详细的说明可参考:http://www.runoob.com/jsp/jsp-jstl.html
其中有个格式化标签需要特别注意下:<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> , 非常的好用

JSTL 中的 EL 函数库

JSTL 也就是官方给的一个库,里面还包含了些 EL 的函数库,一般是用来操作字符串的 ,如果没有,就只能去自定义了
使用之前记得导入:<%@ taglib url="http://java.sun.com/jsp/jstl/functions" prifix="fn" %> ;可以看出是在 fn.tld 的文件中描述的
通过名字基本上也能看出是什么作用来,常见的有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
${fn:toLowerCase(str)}
${fn:trim(str)}

${fn:length(str)}
// 利用 length 的迭代方式
<c:forEach var='i' begin='0' end='${fn:length(list)}' >
${list[i]}
</c:forEach>

${fn:split(str,',')}
// 连接;1.要连接的字符串数组 2.链接符
${fn:join(str,'.')}
// 查找;返回的是 int 索引值
${fn:indexOf(str,'key')}
// 包含;str 中是否包含 key,返回布尔类型,大小写敏感
${fn:contains(str,'key')}
// 是否以指定的 key 开头
${fn:startsWith(str,'key')}
// 替换;1.源字符 2.替换那个字符 3.替换成什么
${fn:startsWith(str,'key','')}
// 截取;从 1 截取到 3,在 java 中最后一个表示的是截取长度
${fn:substring(str,1,3)}
// 截取之前、之后
${fn:substringAfter(str,1)}
${fn:substringBefore(str,1)}
// 转义 HTML
${fn:escapeXml(str)}

关于乱码

中文的一大特色就是乱码咯,乱码产生的原因:

Java 的内核和 class 文件是基于 unicode 的,这使 Java 程序具有良好的跨平台性,但也带来了一些中文乱码问题的麻烦。
首先 Java(包括 JSP)源文件中很可能包含有中文,而 Java 和 JSP 源文件的保存方式是基于字节流的,如果 Java 和 JSP 编译成 class 文件过程中,使用的编码方式与源文件的编码不一致,就会出现乱码。

对于 JSP,在文件头加上 <%@ page contentType="text/html;charset=utf-8"%> ,不设置默认会解析为 iso8859-1,这样基本上就能解决这类乱码问题.
注意,配置的 <%@ page language="java" pageEncoding="utf-8"%> 意思为设置 jsp 文件的存储格式。
保险起见,还可以对请求进行软编码:<%request.seCharacterEncoding("utf-8");%>


对于 Servlet 的乱码,可以直接修改 Tomcat 服务器,conf/server.xml 中的 Connector 节点加入属性:
useBodyEncodingForURL="true"
如果不能修改服务器,那么就只能使用硬编码转换了,因为事先知道默认编码,所以:new String(str.getBytes("ISO-8859-1"),"utf-8");
无论那种,都不要忘了设置软编码:

1
2
3
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");

最好还是用过滤器处理,这样基本就不会出现乱码了。

注意!!!
从 Tomcat8 开始,默认编码已更换为 UTF-8,get 请求含有中文也不会乱码了(需要软编码)!
如果再进行硬编码处理,反而会乱码

其他

如果需要在 web.xml 里面配置相关的映射,因为 JSP 本质还是一个 Servlet,所以按 Servlet 配置就好,只是路径换成 jsp 页面所在的位置即可

javaBean相关

首先,一个良好的 JavaBean 一般都是要有一个空构造函数的

  • <jsp:useBean id="loli" class="com.bf.dd" scope="page"/>
    从 page 域寻找 id 为 loli 的 bean,如果找到就返回,找不到就创建
    如果是个双标签,并且里面如果有内容,只有在创建的时候才执行里面的代码
  • <jsp:setProperty name="id" property="name" value="xxxx"/>
    设置 bean 里面的属性,id 指定那个 bean
  • <jsp:setProperty name="id" property="name" param="xxxx"/>
    将属性设置为URL请求的参数,?name=xxx
  • <jsp:setProperty name="id" property="*"/>
    一次性设置所有的属性,从 url 参数获取,名字要对应
  • <jsp:getProperty name="id" property="name" />
    获取属性

MVC与三层架构

关于这个我在我的公众号确实发过,当然说的也不是太深,简单理解还是够的,这里就只简单说下在 Java 中的体现
它们基本可以应用于任何语言的开发,不过思想都是一样的
有人说 三层架构是属于架构设计,MVC 是属于设计模式,当然也有人说 MVC 也是一种架构,这个不表,我现在还没整明白架构、框架是啥

  • MVC
    M ( Mode ) —-> 其实就是 javabean ;负责数据相关
    V ( View ) —- > 指的就是 jsp;主要是来负责显示
    C ( Controller ) —-> 这个指的就是 servlet 了;用来处理请求,然后转发给 jsp 显示给用户看
  • 三层架构
    Web层 —-> 基本指的就是 Servlet、jsp
    业务逻辑层( Service ) —-> 指的是 service、javabean,用来处理请求、数据的
    数据访问层 ( Dao ) —-> 指的是 dao、javabean,和数据库打交道 JDBC
    各层之间使用接口相联系,上层调用接口,下层实现接口,这样以后如果下层的实现换了以后,上层一行代码都不需要改

在 MVC 或者三层架构下,通常 jsp 的数据是 servlet 带过来的,所以用户不能直接访问 JSP,一般放在 web-inf 文件夹中保护起来
当然,首页的 JSP 肯定是放在外面的

包管理

关于分包,有个模板,不知道现在还用不用了….前面的包名省略了

domain —-> 一般放和数据库相关的 javabean 实体
dao —-> 放数据库相关的接口
dao.impl —-> 与上面的接口对应,是接口的实现
service
service.impl
web.controller
web.ui
web.listener
web.filter
utils
……

开发顺序一般是从下往上,也就是说一般先设计 javabean,然后编写 dao 层,然后是 service 层
先写 impl 层,然后使用 IDE 的抽取接口功能就好了

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~