Jsp compilation process

Source: Internet
Author: User

The j2ee specification provides a specification for jsp Compilation: the first step is to compile an xml file, and the second step is to compile the xml file into a java file.
Example: test. jsp
<%!
Int a = 1;
Private String sayHello () {return "hello ";}
%>
<%
Int a = 1;
%>
<H1> Hello World The first step is to compile it into an xml file. The result is as follows:
<Jsp: declare>
Int a = 1;
Private String sayHello () {return "hello ";}
</Jsp: declare>
<Jsp: scriptlet>
Int a = 1;
</Jsp: scriptlet>
& Lt; h1 & gt; Hello World & lt;/h1 & gt;
Step 3: compile it into a java file. The general result is as follows:
Public class _ xxx_test {
Int a = 1;
Private String sayHello () {return "hello ";}

Public void _ jspService (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

JspWriter out = xxxx. getWriter ();

Int a = 1;
Out. write ("}
}

It can be seen that during the compilation process, the compiler reads the text in turn. when it encounters <% @, it is considered as a jsp instruction, and the instruction is effective for compiling and executing the jsp.
When <%! It considers this statement as a declaration, and the content will directly generate a class attribute or class method of the class. This shows how to write it,
For example: int a = 1; it is considered as a class attribute.

When <% It is encountered, it is considered as a script and will be placed in the default method.

The above is the jsp compilation process, and there is no explanation of how to compile the labels.

When the compiler encounters <%, it reads the subsequent content in sequence until it encounters %>. If the java code contains a string, the content of this string is %>. What should I do?
I know that tomcat will not handle this situation. That is to say, the jsp compiler does not perform syntax checks and only parses strings. The above compiling results are wrong, next, compile it as a class.
When the file is running, it returns a character constant that has not ended. For example:
<%
String s = "test %>"
%>

The compiled results are roughly as follows:
Public class _ xxx_test {
Public void _ jspService (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
JspWriter out = xxxx. getWriter ();
String s = "test
Out. write ("\ r \ n ");
}
}

The j2ee specification also defines that jsp can be written in xml syntax, because jsp is first compiled into xml, in fact, <% is also first compiled into <jsp: scriptlet> therefore, the following two files are equivalent:
File 1:
<%
Int a = 1;
%>
File 2:
<Jsp: scriptlet> int a = 1; </jsp: scriptlet>

But for the specifications, different containers may not always follow the specifications when implementing them. I know that tomcat is based on this, in addition, I remember that in earlier versions of tomcat, the corresponding xml file can be found in the work directory.
However, websphere does not support the current version and does not support the resin. in websphere, <% must be written as <%, and <jsp: script>
Websphere does not compile as xml before compiling as java

The above compilation process is very simple for encoding. If it is not compiled into an xml file, it can be done simply with regular expressions.

EL expression
The support for el expressions is also very simple. When $ {is encountered, it starts to read until} is met. Generate the content into an expression object and directly call the write method of this expression, for example:
Abc $ {user. name} 123

The compilation result is roughly as follows:
Public class _ xxx_test {
Public void _ jspService (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
JspWriter out = xxxx. getWriter ();
ExprEnv exprEnv = xxx. create ();

Out. write ("abc ");
Org. xxx. xxx. Expr _ expr_xxx = xxx. createExpr ("$ {user. name }");
_ Expr_xxx.write (out, exprEnv );
Out. write ("123 \ r \ n ");
}
}

Different containers vary in implementation. For example, resin compiles all expressions into static variables of the class to improve performance. because once a jsp page is written, the number and content of expressions are fixed,
Therefore, it can be compiled into static variables.

I think one of the reasons for compiling to call the write method of the expression instead of compiling to out. write (_ expr_xxx.getValue () is to perform null processing for the expression ,\
If any expression returns null, it should be written to the page as "" instead of "null"
By default, out. write converts the null object to a "null" string for writing. If it is compiled into out. write (_ expr_xxx.getValue ()),
You need out. write (_ expr_xxx.getValue ()! = Null? _ Expr_xxx.getValue ():""));
Obviously, this affects the performance, because if the returned result is not null, the expression may be calculated twice.
If you do not do this, you need to redefine the variables. To avoid variable conflicts, the compiler generates a new variable name in each place, resulting in a large file.

Tag Compilation
Compilation of tags is a little complicated, but not complicated. This requires html parsing of the source file. However, compared with a complete html Parser, tag Parsing is much easier.
You only need to read the node name when the '<' character is encountered, and then find the corresponding tag class in the tag Library supported by the current application. If not, continue to compile as out according to the above. write ("<");
Otherwise, read all attributes, create a tag instance, and then call the corresponding setter Method Based on the defined attributes and the attributes defined in the tag. For example:
<C: if test = "$ {user. name = 'Tom '}"> The compilation result is roughly as follows:
Expr expr_0 = xxx. createExpr ("$ {user. name = 'Tom '}");
Tag _ tag_0 = new xxx. xxx. IfTag ();

_ Tag_0.setter (...);

Int _ tag_flag_0 = _ tag_0.doStartTag ();

If (_ tag_flag_0! = SKIP_BODY)
{
While (true)
{
// DoInitBody, doBody, etc.
_ Tag_flag_0 = _ tag_0.doEndTag ();

// DoAfterBody

If (_ tag_flag_0! = EVAL_BODY_AGAIN)
{
Break;
}
}
}
The above is a standard process for running tags. In fact, the compilation results vary greatly for different containers, such as resin. The actual compilation results are roughly as follows:

Expr expr_0 = xxx. createExpr ("$ {user. name = 'Tom '}");

If (expr_0.getBoolean ())
{
}

The compilation result is very simple. In addition to the forEach compilation for the core j2ee tag library, all the other statements are compiled into very simple code without loops.
This may be to reduce compilation results and improve performance.
For most labels, there is no need to compile according to the standard tag execution process. For the labels defined in the core tag library, the compilation results can be simplified because the behavior is clear.
Tomcat uses the do... while structure. resin to compile tags in the _ jspService method.

The end of the tag. How do I know that the tag has ended during tag compilation? A simple idea is that if a start tag is encountered, it will be read until the end tag is encountered. Obviously, this will not work.
Because tags are nested, what should I do if I encounter nested tags? Follow the process above to read the sub-tag, read the sub-tag, and then? A little understanding of the data structure makes it easy to use stacks.
For the same problem, the general solution is the same, such as calculators, such as html and xml parser. For the html Parser, I will write another article to illustrate this topic.
Create a stack first. When a tag is encountered, press it into the stack first. The element content is defined as needed. We assume that the structure is as follows:
Class TagInfo {
String nodeName; // node name
Map <String, String> attributes; // node attributes such as: test :$ {user. name = 'Tom '}
Map <String, String> variables; // list of variables that may be used by the current tag, such as flagName: _ flag_0 and exprName: expr_0.
}
Note that the TagInfo is pushed to the stack.

When an end tag is encountered, the nodeName of the end tag is obtained, and an element pops up from the stack. If tagInfo. nodeName = nodeName, the code for the end tag is generated.
For the standard tag process, you only need to generate the following code:

// Out. write ("// The previous code may be like out. write.
// _ Tag_flag_0 and other variables are obtained from tagInfo
_ Tag_flag_0 = _ tag_0.doEndTag ();

// DoAfterBody

If (_ tag_flag_0! = EVAL_BODY_AGAIN)
{
Break;
}
}
}
If the current nodeName! = TagInfo. nodeName,
In fact, the final running result of the page is inconsistent with the expectation of the jsp writer.
If no exception is found until the bottom of the stack.

For a stack, you do not need pop many times. You only need to check whether the top of the stack meets the requirements and pop only when it meets the requirements. Otherwise, you need to push the top of the stack if it does not meet the requirements. This is very troublesome.
Therefore, it is better to provide a peek function for the stack and input an int. The default value is the top of the stack. The element of the current stack is returned Based on the parameter, which is more convenient.

Finally, in jsp, the specification stipulates that all variables starting with _ jsp cannot be used, which is reserved for APIS or containers.

The above is an analysis of the jsp compilation process. For the j2ee specification definition, I have not read the original article, but I have read some scattered things from some java books. What's more
Looking at the analysis and speculation of java source files compiled by containers, the ideas may be different from those defined in j2ee specifications in many places. If you are interested, you can find them on the java official website.
See the original specification.

In, I used java to implement a set of tomcat-like containers. Of course, the functions are much weaker. It only supports some basic functions and can run jsp and servlet, el and tag are not supported.
Even worse, I was just working at the time and had poor control over a project with a large amount of code. Finally, I thought the architecture was too inadequate, zookeeper was able to run jsp and servlet without continuing.
At that time, I was not familiar with socket nio. socket I/O was used to block I/O, And the thread pool was not used. Every time it was a new thread, the performance was very poor.
If you are interested, refer to my other articles and use java to implement reverse proxy. The code is part of the code of the year.

Let's talk about the js version of jstl.
The js version of jstl is basically implemented according to the analysis above. It supports scripts, el, tag, and custom tags.
For the sake of performance, the concept of resin is used for tag compilation. For standard labels, compilation is simplified instead of the standard process.
For the sake of performance, the compilation process omitted the intermediate step, that is, directly compiling as the js source file instead of compiling as xml.
If xml is generated during the compilation process, an xml file is generated in the memory for large files, and then compiled as a js file again.
Compilation is required twice in the middle, consuming memory and resources.

El is supported by a lazy method. for example, abc {user. code such as name} 123 must be written as abc {this. user. name> 123
This must be added to all attributes in pageContext;
This is related to implementation. It is very troublesome to calculate el. You need to write an interpreter. Otherwise, simple parsing will be powerless for complicated expressions.
For example, $ {user. name} is easy to calculate, but for ${myfun1 (user. name) + myfun2 ('test') + myfun3 ('test ')}
Or % {user. age> 100*2} is quite troublesome, and no interpreter can be used.
I initially considered using eval, but eval has poor performance in some environments, and if there are a lot of el in the compiled results, it will be called many times.
More importantly, eval cannot be used. For example, eval ("user. name + '000000'") has no user object in the global environment.
However, if this is added, then eval can be used.

But eval cannot be used. The eval overhead is too large.
Writing an interpreter is unrealistic and unnecessary. In order to support expressions, it is not cost-effective to write another interpreter in an interpreted language.
Finally, we adopted a compromise method, that is, the object in pageContext. this is added to el, that is, all this in el points to pageContext.
Each expression generates an expression object, which is consistent with the definition in j2ee. Another function is generated, for example:
Abc $ {this. user. name} 123

The final compilation result is roughly as follows:
New (function (){
This. handle = function (pageContext ){
Var out = this. getWriter ();
Out. write ("abc ");
This. _ expr_0.write (out, pageContext );
Out. write ("123 ");
})();
// All expression objects generated during the compilation process
This. _ expr_0 = new Expression ("_ expr_0", "this. user. name ");

// Reference of all expression objects generated during compilation is recorded here
This. exprPool = [
This. _ expr_0
];
// This is the key. It is all expression functions, which are currently null and will be compiled at the first run.
This. exprList = null;

During the first running, the system checks whether this. exprList is null. If it is null, all expressions are compiled. The compilation result is as follows:
This. exprList = new (function (){
This. _ expr_0 = function (){
// This. _ expr_0 function will be put into pageContext, which is why this is used.
Return (this. user. name );
}
})();

This. exprList points to a new object, which must be an object.
Next, the runtime:
Scriptlet.exe cute (context );
Context is passed in by the caller. It can be a pure json object. The scriptlet.exe cute method is as follows:
// Scriptlet points to the object returned by the first compilation
// Scriptlet creates the execute method in new
Scriptlet.exe cute = function (context ){
Var pageContext = PageContextFactory. create (this, context, this. exprList );
This. handle (pageContext );
};

In the PageContextFactory. create method, the context is encapsulated, a new object is created, and all attributes of the context are assigned to the new pageContext.
Then assign all functions contained in the exprList to the new pageContext, so that the pageContext has all the attributes of the context and the scriptlet Runtime
All expression functions are required. this points to pageContext, which is why this is used in el.


 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.