getDependants() { return _jspx_dependants; } public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } public void _jspDestroy() { } 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("\n"); out.write("\n"); out.write("\n"); out.write("\n"); out.write("\n"); out.write("\n"); out.write("\n"); out.write("\n"); out.write("\n"); out.write("Welcome, my friend!\n"); out.write("\n"); out.write("\n"); out.write(""); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } } View Code
发现熟悉的 out.write 又回来了, 只是此时的 out.write 不是我们手动写的, 而是 Tomcat 解析 jsp 后生成的; 如果 jsp 没变动, jsp 只会在第一次被调用时解析, 编译一次, 后续的请求都会由编译后的 servlet 处理, 我们来验证下, 如何验证了? 不变 index.jsp 内容再请求 index.jsp, 看看上图中文件的修改时间会不会变
发现文件的修改时间没有变动, 也就是说上面的的结论: 如果 jsp 没变动, jsp 只会在第一次被调用时解析, 编译一次是对的. 感兴趣的朋友可以去看下 Tomcat 的源码, 看看具体的实现细节.
有人可能会问: 为什么不将 jsp 的内容直接返回给浏览器? 我们要明白一点: 浏览器只能解析 HTML,CSS,JS, 除此之外的内容它解析不了, 那么我们能直接将 jsp 的内容返回给浏览器吗? 所以中间有处理过程, 最终由 servlet 将静态内容返回给浏览器. 有些爱问的小伙伴可能又会问了: 浏览器为什么只能解析: HTML,CSS,JS, 这涉及到浏览器规范的问题, 除非你有能力改变这个规范, 让浏览器支持你想要的内容, 这个问题不做过深的讨论, 我们姑且认为这是浏览器的限制, 既然我们改变不了这个限制, 那就适应这个限制.
Servlet 四大作用域与 JSP 九大内置对象
Servlet 四大作用域包括: page 域, request 域, session 域, application 域, 作用域指的是变量的有效期限, 具体如下
当变量的作用域是 page, 它的有效范围只在当前 jsp 页面里有效;
JSP 九大内置对象包括: page,request ,response,pageContext,session,application,out,config,exception, 内置对象指的是 Servlet 容器创建的一组对象, 不需使用 new 关键字就可以直接使用的内置对象.
四大作用域与九大内置对象对应关系如下
更多详情需要大家自己去查阅资料了
EL 表达式与 JSTL 标签
我们知道 jsp 中可以插入 Java 代码片段, 类似如下
- <%pageContext.setAttribute("sex", "男"); %>
- <%=pageContext.getAttribute("sex") %>
- Welcome, my friend!
其中 <% %> 包裹的就是 java 片段,<%= %>输出表达式值到页面; 可以看到不够简洁, 阅读性也不太友好, 所以 EL 表达式就应运而生了, 上述代码可以替换成如下代码
- <%pageContext.setAttribute("sex", "男"); %>
- ${sex} Welcome, my friend!
EL 能够访问页面的上下文以及不同作用域中的对象 , 取得对象属性的值, 或执行简单的运算或判断操作, 用来简化 JSP 中的 java 代码. EL 表达式是 JSP1.2 之后内置支持的, 可以直接在 JSP 中使用, 它从 servlet 四大作用域 (范围 servletContext> session> request> pageContext) 中取值, 这四个域都有 setAttribute("",object)方法和 getAttribute("")方法, EL 表达式会自动按作用范围从小到大的顺序从四大作用域中寻找对应名字的值, 找到了就立即返回不再继续寻找, 其内部调用的就是 pageContext 的 findAttribute("")方法.
EL 固然能简化 JSP 中的 java 代码, 但是它功能非常简单, 不能满足一些复杂的代码逻辑, 所以就诞生了 JSTL.JSP 标准标签库 (JSTL) 是一个 JSP 标签集合, 它封装了 JSP 应用的通用核心功能, 支持通用的, 结构化的任务, 比如迭代, 条件判断, xml 文档操作, 国际化标签, SQL 标签, 另外还支持自定义标签, 它实现了 JSP 页面中的代码复用, 简化了代码的书写, 同时也保证了 JSP 的可读性更强. JSTL 功能比较丰富, 但它不是 JSP 内置支持的, 所以需要导入标签库到 JSP 页面(还要添加 jstl 的 jar 包依赖).JSTL 往往会集合 EL 表达式来使用, 简单示例如下
- <%@ page language="java" contentType="text/html; charset=utf-8"
- pageEncoding="utf-8"%>
- <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
- Hello, ${sex}
- Hi, girl
这代码看起来就清爽多了, 没有 java 代码, 前端开发者也很容易看懂; 关于 EL 表达式与 JSTL 标签更详细信息, 需要大家自行去查阅资料了, 本文篇幅有限, 不做过多的讲解了.
重定向与请求转发
那么可想而知, 重定向的 request 作用域的变量是会失效的, 而转发则不会
Spring MVC
还记得我们是如何配置 Spring MVC 的吗, 我们会在 web.xml 中配置如下代码
- springDispatcherServlet
- org.springframework.Web.servlet.DispatcherServlet
- contextConfigLocation
- classpath:spring-mvc.xml
- 1
- springDispatcherServlet
- /
这样就配置了 Spring MVC; 大家可以留意下 DispatcherServlet, 去看他的类图会发现, 他就是一个 Servlet 的实现, 也就是说 Sprinv MVC 就是基于 Servlet 的拓展.
我们在 Spring MVC 基础上进行开发的时候, 将数据绑定到作用域的时候, 一般用的是 SpringMVC 的数据模型: Model 或者 ModelMap, 例如这样
- @RequestMapping("/showPerson")
- public String showPersons(Model model){
- List persons = personService.loadPersons();
- model.addAttribute("persons", persons); // 绑定数据到视图
- return "showperson";
- }
而不是显示的直接绑定到 Servlet 四大作用域, 数据难道没有绑定到四大作用域? 我们说过, EL 表达式只能在四大作用域中取值, 否则取不到, 所以 SpringMVC 中的数据绑定最终还是会到四大作用域的某一个中, 至于是何时, 何地, 如何将 Model 中的属性绑定到哪个作用域, 这个不是本文要说的了, 篇幅太大了, 有机会再重开一篇博客给大家讲下, 给大家点提示: spring3.2 之前去查看 AnnotationMethodHandlerAdapter,spring3.2 及之后去查看: RequestMappingHandlerAdapter. 这里给个结论: 在默认情况下, Model 中的属性作用域是 request 级别.
问题解答
有些小伙伴会抱怨了: 上面哔哔了那么多, 怎么就是不讲答案, 净说一些没用的
如果大家坚持看到这了, 再坚持会, 答案马上揭晓, 上面铺垫了那么多, 绝对是有用的.
我们回到问题: 当我们请求 http://localhost: 端口 / 工程名 / personController/showPerson 时, 数据正常显示, 而当我们直接请求 jsp 时, 只有 title 却没有数据, 这是为什么? title 是静态页面内容, 这个不用管, 那为什么直接请求 jsp 为什么没有数据库的 person 列表呢? jsp 源代码如下
- <%@ page language="java" contentType="text/html; charset=utf-8"
- pageEncoding="utf-8"%>
- <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
姓名
年龄
- ${person.name }
- ${person.age }
里面用到了和 EL 表达式, 解释下这个流程: EL 表达式先从四大作用域获取名为 persons 的集合, 然后遍历该集合, 每次遍历的结果放到 page 作用域, 并取名叫 person, 最后通过 EL 表达式输出 person 的 name 和 age 到页面. 那么请问: 直接访问 JSP, 四大作用域中有名叫 persons 的属性吗? 很显然没有, persons 不存在, 遍历它会有结果输出吗? 这就是为什么直接访问 jsp 没有数据的答案.
我们再回到 Controller 层
- @RequestMapping("/showPerson")
- public String showPersons(Model model){
- List persons = personService.loadPersons(); // 从数据获取 person 列表, 并存放到了 persons 集合中
- model.addAttribute("persons", persons); // 将 persons 集合添加到 model 的 persons 属性中
- return "showperson"; // 转发到 showperson.jsp
- }
代码也非常简单, 先从数据库获取 person 集合, 然后将该集合设置到了 model 的属性 persons 中, 我们知道 model 的属性默认情况下会设置到 request 作用域; 然后将请求转发到 showperson.jsp, 转发过程中, request 作用域的变量仍然有效, 所以 jsp 中 EL 表达式能够读取到 persons 变量, 所以就有数据输出到页面了.
总结
1,Servlet 与 Servlet 容器的关系比较暧昧, 两者相互作用, 实现 Web 服务; 简单点说, 我们自定义的 Servlet 就是对 Servlet 容器的业务拓展, 而 Servlet 容器是对 Servlet 的支撑;
2,JSP 的出现时为了简化静态页面的开发, EL 表达式与 JSTL 的出现则是为了简化 JSP 页面的 Java 代码; JSP 本质还是 Servlet, 在第一次被访问的时候会被 Servlet 容器解析成 Servlet, 编译 Servlet, 最终还是有 Servlet 将页面内容 out.write 到浏览器;
3,Spring MVC 本质还是 Servlet, 它的出现是为了简化 Web 开发, 同时可以与 spring 无缝对接, 享受 spring 带来的好处; Spring MVC 的数据绑定, 依托的还是 Servlet 的的四大作用域, 只是中间存在转换过程;
4,EL 表达式的取值必须存在于四大作用域中, 在 jsp 中用 EL 表达式时, 一定要保证数据正确地添加到了四大作用域中, 不然, EL 表达式会取不到值;
参考
《深入分析 JavaWeb 技术内幕》
《Tomcat 系统架构与模式设计分析》
Java Web(一) Servlet 详解!!
来源: https://www.2cto.com/kf/201904/801308.html