从JSP开始谈模板引擎

JSP即Java Server Page,本质是Servlet。要讲JSP还是要先说Servlet,要说Servlet则离不开Tomcat,要谈Tomcat还是要从HTTP协议说起。

一、HTTP协议

1、简述与特点

       HTTP协议是基于TCP/IP的应用层协议。

       HTTP协议是无状态协议,协议本身并不保留之前一切的请求或响应报文的信息,为了保存一些信息,引入了Cookie。

       HTTP协议是无连接协议。无连接又分为两种,HTTP/1.0和HTTP/1.1。1.0的时候,每一次请求都会建立一个连接,传完就关闭,下次重新开启个新的连接。现在的HTTP/1.1版本不是直接就断开了,而是等几秒钟,如果用户在这几秒钟之内有新的请求,那么还是通过之前的连接通道来收发消息,如果过了这几秒钟用户没有发送新的请求,那么就会断开连接。其实一般的web网站都是短连接,实时视频这种肯定是长连接了。

2、流程梳理

       用户在浏览器上输入URL,进而DNS域名解析器会工作,将域名解析成对应的IP地址,客户端与Web服务器建立TCP的套接字连接(三次握手)。服务端接收并建立连接后,客户端生成HTTP格式的数据包作为请求报文发给服务端,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据等,服务端解析HTTP格式的数据包。之后在Servlet容器中找到对应的Servlet并执行业务方法,执行完后生成为HTTP格式的数据传回给客户端浏览器,浏览器再去解析HTTP格式的数据,之后浏览器再去渲染页面呈现给用户。
Alt

3、HTTP状态码

       状态代码的第一个数字代表当前响应的类型:
              1xx消息——请求已被服务器接收,继续处理
              2xx成功——请求已成功被服务器接收、理解、并接受
              3xx重定向——需要后续操作才能完成这一请求
              4xx客户端请求错误——客户端请求错误或者无法被执行
              5xx服务器错误——服务器在处理某个正确请求时发生错误



二、Tomcat详解

1、流程梳理

       HTTP请求进来不是直接调业务方法,而是先从Servlet容器中找是否有请求所对应的Servlet,没找到就反射new一个出来加到容器中,然后再调用此Servlet的业务方法。和Spring容器一样,这样搞最大的好处就是解耦
Alt

tomcat的主启动类是BootStrap的main方法


2、整体结构

       连接器即Coyoto封装了最底层的网络通信(Socket),还负责网络字节流传输并将Request对象转化为ServletRequest对象后发给容器即Catalina。

Alt
Alt

       Engine:一个service最多只有一个引擎
       Host:对应于一个主机
       Context:对应一个应用
       Wrapper:对应一个Servlet
Alt

       看了上图再看tomcat的配置文件server.xml就豁然开朗了。

	<Server port="8005" shutdown="SHUTDOWN">
		<Service name="Catalina">
			<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
               
            <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               keystoreFile="E:\apache-tomcat_key\tomcat.keystore" keystorePass="123456"
               clientAuth="false" sslProtocol="TLS" />
               
            <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
            
				<Engine name="Catalina" defaultHost="localhost">
					<Host name="localhost"  appBase="webapps"
           				unpackWARs="true" autoDeploy="true">
            		</Host>
    			</Engine>
  		</Service>
	</Server>

       另外上面的容器结构图中Host下面还可以配置Context。看到tomcat的conf文件夹下面还有context.xml配置文件。
Alt

<Context>

    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
</Context>

       其实可以看出所有发布的应用都会归当前应用下的WEB-INF/web.xmlE:\apache-tomcat-8.5.41\conf\web.xml管理?

       通常一个Catalina容器可以有多个服务,可以在Host标签中的appBase属性中设置发布路径

       IDEA这种IDE上配置的端口其实只是暂时让我能通过这个IP地址+端口号访问我们的应用,最后打成的war包还是要放到linux服务器上面的,最后还是要靠最终服务器上的配置文件来进行管理。war包放到webapps下面会自动解压缩,而且一旦删除war包连带的文件夹也会被删除掉。



三、JSP

1、原理分析

       jsp本质就是Servlet。

       tomcat下的web.xml中配置了映射关系,只要是.jsp以及.jspx结尾的URL都会交给JspServlet处理该请求。

	<servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>

	<servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>


2、看下源码咯

       JspServlet的service方法:

	public void service (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // jspFile may be configured as an init-param for this servlet instance
        String jspUri = jspFile;

        if (jspUri == null) {
            /*
             * Check to see if the requested JSP has been the target of a
             * RequestDispatcher.include()
             */
            jspUri = (String) request.getAttribute(
                    RequestDispatcher.INCLUDE_SERVLET_PATH);
            if (jspUri != null) {
                /*
                 * Requested JSP has been target of
                 * RequestDispatcher.include(). Its path is assembled from the
                 * relevant javax.servlet.include.* request attributes
                 */
                String pathInfo = (String) request.getAttribute(
                        RequestDispatcher.INCLUDE_PATH_INFO);
                if (pathInfo != null) {
                    jspUri += pathInfo;
                }
            } else {
                /*
                 * Requested JSP has not been the target of a
                 * RequestDispatcher.include(). Reconstruct its path from the
                 * request's getServletPath() and getPathInfo()
                 */
                jspUri = request.getServletPath();
                String pathInfo = request.getPathInfo();
                if (pathInfo != null) {
                    jspUri += pathInfo;
                }
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("JspEngine --> " + jspUri);
            log.debug("\t     ServletPath: " + request.getServletPath());
            log.debug("\t        PathInfo: " + request.getPathInfo());
            log.debug("\t        RealPath: " + context.getRealPath(jspUri));
            log.debug("\t      RequestURI: " + request.getRequestURI());
            log.debug("\t     QueryString: " + request.getQueryString());
        }

        try {
            boolean precompile = preCompile(request);	//只是预编译
            //主要是这个方法
            serviceJspFile(request, response, jspUri, precompile);
        } catch (RuntimeException e) {
            throw e;
        } catch (ServletException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(e);
        }
    }

       JspServlet的serviceJspFile方法:

	private void serviceJspFile(HttpServletRequest request,
                                HttpServletResponse response, String jspUri,
                                boolean precompile)
        throws ServletException, IOException {

        JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
        if (wrapper == null) {
            synchronized(this) {
                wrapper = rctxt.getWrapper(jspUri);
                if (wrapper == null) {
                    // Check if the requested JSP page exists, to avoid
                    // creating unnecessary directories and files.
                    if (null == context.getResource(jspUri)) {
                        handleMissingResource(request, response, jspUri);
                        return;
                    }
                    wrapper = new JspServletWrapper(config, options, jspUri,
                                                    rctxt);
                    rctxt.addWrapper(jspUri,wrapper);
                }
            }
        }

        try {
        	//主要是这个方法
            wrapper.service(request, response, precompile);
        } catch (FileNotFoundException fnfe) {
            handleMissingResource(request, response, jspUri);
        }

    }

       JspServletWrapper的service方法:

	public void service(HttpServletRequest request,
                        HttpServletResponse response,
                        boolean precompile)
            throws ServletException, IOException, FileNotFoundException {

        Servlet servlet;
        ...
        try {
			if (options.getDevelopment() || mustCompile) {
                synchronized (this) {
                    if (options.getDevelopment() || mustCompile) {
                        // The following sets reload to true, if necessary
                        ctxt.compile();		//生成对应的java文件和字节码文件
                        mustCompile = false;
                    }
                }
            } else {
                if (compileException != null) {
                    // Throw cached compilation exception
                    throw compileException;
                }
            }

            /*
             * (2) (Re)load servlet class file
             */
            servlet = getServlet();		//重新加载路径下编译好的字节码文件
            ...
            if (servlet instanceof SingleThreadModel) {
               // sync on the wrapper so that the freshness
               // of the page is determined right before servicing
               synchronized (this) {
                   servlet.service(request, response);	//最终用PrintOut对象输出到浏览器
                }
            } else {
                servlet.service(request, response);
            }
		} catch(Eeception e) {
			...
		}

       虽然很绕,但问题不是很大,快要绕出来了!

       JspCompilationContext的compile方法:

	public void compile() throws JasperException, FileNotFoundException {
        createCompiler();
        if (jspCompiler.isOutDated()) {
            if (isRemoved()) {
                throw new FileNotFoundException(jspUri);
            }
            try {
                jspCompiler.removeGeneratedFiles();
                jspLoader = null;
                jspCompiler.compile();
                jsw.setReload(true);
                jsw.setCompilationException(null);
            } catch (JasperException ex) {
                // Cache compilation exception
                jsw.setCompilationException(ex);
                if (options.getDevelopment() && options.getRecompileOnFail()) {
                    // Force a recompilation attempt on next access
                    jsw.setLastModificationTest(-1);
                }
                throw ex;
            } catch (FileNotFoundException fnfe) {
                // Re-throw to let caller handle this - will result in a 404
                throw fnfe;
            } catch (Exception ex) {
                JasperException je = new JasperException(
                        Localizer.getMessage("jsp.error.unable.compile"),
                        ex);
                // Cache compilation exception
                jsw.setCompilationException(je);
                throw je;
            }
        }
    }

       Compiler的compile方法:

	public void compile(boolean compileClass, boolean jspcMode)
            throws FileNotFoundException, JasperException, Exception {
        if (errDispatcher == null) {
            this.errDispatcher = new ErrorDispatcher(jspcMode);
        }

        try {
            final Long jspLastModified = ctxt.getLastModified(ctxt.getJspFile());
            //生成java文件
            String[] smap = generateJava();		
            File javaFile = new File(ctxt.getServletJavaFileName());
            if (!javaFile.setLastModified(jspLastModified.longValue())) {
                throw new JasperException(Localizer.getMessage("jsp.error.setLastModified", javaFile));
            }
            if (compileClass) {
            	//生成字节码文件
                generateClass(smap);
                // Fix for bugzilla 41606
                // Set JspServletWrapper.servletClassLastModifiedTime after successful compile
                File targetFile = new File(ctxt.getClassFileName());
                if (targetFile.exists()) {
                    if (!targetFile.setLastModified(jspLastModified.longValue())) {
                        throw new JasperException(
                                Localizer.getMessage("jsp.error.setLastModified", targetFile));
                    }
                    if (jsw != null) {
                        jsw.setServletClassLastModifiedTime(
                                jspLastModified.longValue());
                    }
                }
            }
        } finally {
            if (tfp != null && ctxt.isPrototypeMode()) {
                tfp.removeProtoTypeFiles(null);
            }
            // Make sure these object which are only used during the
            // generation and compilation of the JSP page get
            // dereferenced so that they can be GC'd and reduce the
            // memory footprint.
            tfp = null;
            errDispatcher = null;
            pageInfo = null;

            // Only get rid of the pageNodes if in production.
            // In development mode, they are used for detailed
            // error messages.
            // http://bz.apache.org/bugzilla/show_bug.cgi?id=37062
            if (!this.options.getDevelopment()) {
                pageNodes = null;
            }

            if (ctxt.getWriter() != null) {
                ctxt.getWriter().close();
                ctxt.setWriter(null);
            }
        }
    }

       找到IDEA对于对应路径下面的文件打开查看。果然是PrintOut对象调用write()方法直接写到浏览器上
Alt
       index_jsp的__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");
	      //看源码终于明白了为啥SpringMVC的Rest风格会在tomcat7以上报错, 需要在头部加上isErrorPage="true"
	      return;
	    }
		...
		out.write("\n");
	    out.write("\n");
	    out.write("<html>\n");
	    out.write("  <head>\n");
	    out.write("    <title>$Title$</title>\n");
	    out.write("  </head>\n");
	    out.write("  <body>\n");
	    out.write("  $END$\n");
	    out.write("  </body>\n");
	    out.write("</html>\n");
	    ...
	    //说到底最后就是PrintOut对象输出



3、JSP的九大内置对象
内置对象作用
request传递数据, 作用域为一次请求
response传递数据, 作用域仅在JSP页面
session服务器为每个用户都生成一个对象(默认过期时间为30分钟)
application服务器为每个应用都生成一个对象
out用于在Web浏览器内输出信息
pageContext初始化由容器完成, JSP页面可直接用该对象。通过它可以获取 JSP页面的out、request、reponse、session、application 等对象
config取得服务器的配置信息, 通过pageConext.getServletConfig()可以获取该对象
pagepage 对象代表JSP本身, 类似于this指针
exception用于显示异常信息, 包含 isErrorPage=“true” 的页面中才可以被使用



四、Thymeleaf模板引擎

1、简介

       就是jsp的升级版,是一个模板引擎(java语言编写),可以处理html、xml、js、css等等,特别是html5。既可以展示静态数据也可以用后端传的数据直接展示,也可以实现前后端分离。不过也有缺点,Thymeleaf都是后端渲染的数据,渲染整个html文档,服务器压力大,Vue的只需要ajax请求数据,后端只需要渲染json数据就好了,服务器压力小很多。

       这种模板引擎底层绝对逃不开Servlet。

       需要引入名称空间<html lang="en" xmlns:th="http://www.thymeleaf.org">



2、源码(只看前后缀)
	public class ThymeleafProperties {
	    private static final Charset DEFAULT_ENCODING;
	    public static final String DEFAULT_PREFIX = "classpath:/templates/";	//前缀
	    public static final String DEFAULT_SUFFIX = ".html";	//后缀
	    private boolean checkTemplate = true;
	    private boolean checkTemplateLocation = true;
	    private String prefix = "classpath:/templates/";	//前缀
	    private String suffix = ".html";	//后缀
	    private String mode = "HTML";
	    private Charset encoding;
	    private boolean cache;
	    private Integer templateResolverOrder;
	    private String[] viewNames;
	    private String[] excludedViewNames;
	    private boolean enableSpringElCompiler;
	    private boolean renderHiddenMarkersBeforeCheckboxes;
	    private boolean enabled;
	    private final ThymeleafProperties.Servlet servlet;
	    private final ThymeleafProperties.Reactive reactive;
	    ...
}



3、基本语法及内置对象

       基本语法:

语法描述作用
${...}变量表达式取出上下文变量的值
*{...}选择变量表达式取出选择对象的属性值
#{...}消息表达式取出国际化消息
@{...}链接表达式表示超链接地址
       内置的对象:
对象描述
#ctx上下文对象
#var上下文变量
#locale上下文地理区域对象
#requestHttpServletRequest对象
#responseHttpServletResponse 对象
#sessionHttpSession 对象
#servletContextServletContext 对象


3、简单demo

       html界面:

	<!DOCTYPE html>
	<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
	    <meta charset="UTF-8">
	    <title>Hello</title>
	</head>
	<body>
	    <div th:text="${msg}"></div>
	    <div>[(${msg})]</div>
	    <hr>
	
	    <h2 th:each="user:${users}">[(${user})]</h2>
	    <hr>
	
	    <h2>
	        <span th:each="user:${users}">[[${user}]]</span>
	    </h2>
	    <hr>
	
	    <div th:object="${person}">
	        <h2 th:text="*{name}"></h2>
	        <h2>[[*{age}]]</h2>
	    </div>
	    <hr>
	
	    <div th:text="${#request.getAttribute('msg')}"></div>
	</body>
	</html>

       controller:

	@RequestMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("msg", "<h2>哈哈哈</h2>");
        List<String> users = new CopyOnWriteArrayList<String>();
        users.add("张三");
        users.add("李四");
        users.add("王五");
        model.addAttribute("users", users);
        Person p = new Person();
        p.setAge(18);
        p.setName("xxx");
        model.addAttribute("person", p);
        return "hello";
    }

运行结果:
Alt
       都是用th作为前缀,text表示对特殊字符转译并写入文本(转译后可以原样输出),utext是不对特殊字符进行转译并写入文本。

       属性上的th:text等价于行内写法[[${...}]]。属性上的th:utext等价于行内写法[(${...})]

SpringBoot可以集成Thymeleaf、Freemaker等等,但不支持JSP。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值