Struts2's structure
1. Why use frameworks?
(1) The framework automatically completes many trivial tasks
For Struts2, it helps us easily complete data type conversion, data verification, internationalization, etc.
Common tasks in web development. There are also the Template modes that are widely used in Spring, which are all making our development process more automated and intelligent. Using a framework is to avoid reinventing the wheel and re-copy these template codes.
Framework allows us to focus more on higher-level issues than on common workflows and basic tasks.
(2) Using a framework means gracefully inheriting the architecture behind the framework
The architecture behind the framework usually defines a series of workflows. What we need to do is to attach the code of a specific application to this process, so that we can enjoy the various benefits brought by the framework. Sometimes we can also resist the framework's architectural rules, but the framework usually provides its architecture in a way that is difficult to reject. It is so simple that you can inherit an excellent architecture gracefully and it is free, so why not do it?
(3) It is easier to find well-trained people using the framework
I have almost never used any frameworks in the entire project of my company before, and searching for Service services (similar to JNDI)
To log printing (similar to Log4j), and then to database connection pool (similar to DBCP), all of them are implemented by internal personnel themselves. Firstly, it is because the project is relatively old, and there may not be any open source framework for use at that time. Secondly, it is because of the company's conservative strategy, and it is worried that using an unstable open source framework may bring risks to the project. This may be true in the environment at that time, and the company's senior management will naturally consider the entire project from a larger perspective.
However, when the project gradually becomes larger and there are more and more excellent open source frameworks in the world, if some mature open source frameworks cannot be refactored in time and introduced, the final result may be that newly recruited developers must learn this complex system from scratch (all internal systems, and there is no help on the Internet), and be careful of various bugs in the internal framework.
The cost is really too high.
(4) The internal framework cannot keep up with the development of the industry
The bug in the internal framework mentioned earlier. For open source frameworks, there may be a team of framework founders, a large number of open source enthusiasts,
Open source community to support it. The power of the people is infinite, and the speed of bug repair can be imagined. This is from the recent open source
The bug fixing process of TextMate can be seen. Many bugs that have been shelved for a long time have been quickly solved by enthusiasts after they are open source, but what about the internal framework? After the people who developed him left the company, no one would even read his source code without major bugs. The gap is evident!
(5) Of course, using a framework is not a huge profit.
As mentioned earlier, using an immature framework is risky, and it is better to be conservative for a project that is not so radical.
(Unless this is a group of free and unrestrained tech fanatics who can decide what framework to use at their own discretion, that is really a blessing)
Just like Sequioa, the Java HA high availability service I used before, this framework is no longer supported by the development company, and the risk is even greater.
In addition, when using some uncommon frameworks, you should also pay attention to the framework source code License protocol, and do not reference it at will in the project.
Modify the source code of the framework to avoid unnecessary legal disputes.
2. The architecture behind Struts2
Since we have analyzed so many benefits of the framework before, we will naturally start to learn to use Struts2. But using Struts2
What kind of elegant architecture will it inherit? In fact, from a higher level of abstraction, it is still the MVC model we are familiar with.
According to the previous HelloWorld example, controller C (FilterDispatcher) is what we declared in web.xml
Struts2 core class. And model M is our NewsAction action class. And view V is naturally news.jsp. The concept of the model seems a bit vague. What is a model? In fact, this concept that sounds very noun contains both business data statically transmitted from the web front-end and the implementation of business logic.
Some people may say that this architecture is not new, there are many MVC frameworks, what is the difference between this and other frameworks? Let's dissect Struts2 at a lower level of abstraction and see what makes it unique.
At first glance, it looks very complicated. If we only look at it from the user's perspective, we only need to implement the yellow part during development, that is, we
struts.xml, NewsAction and news.jsp in HelloWorld instance. This is all we have to do, as mentioned earlier, we only need to do very little things and we become part of this excellent architecture.
Now look at the other parts. FilterDispatcher is the Servlet filter we configure in web.xml, which is Struts2
All Struts2 web applications must be configured in this way. Next, the blue and green parts are the core of Struts2. It can be said that these classes are carefully designed by the developers of Struts2.
(1) The client sends a request, and the J2EE container parses the HTTP packet and encapsulates it into an HttpServletRequest.
(2) FilterDispatcher intercepts this request and searches the ActionMapper based on the request path to determine which Action to call.
(3) According to the return result of ActionMapper, FilterDispatcher entrusts ActionProxy to find this Action in struts.xml.
(4) ActionProxy creates an ActionInvocation and starts recursive calls to Interceptor and Action.
(5) Each Interceptor completes its own tasks
(6) The real call to Action returns the result path
(7) The Result object will output the return data to the stream
(8) Return HttpServletResponse to the J2EE container, and the container sends HTTP packets to the client.
This is the execution process of Struts2. The core objects are ActionInvocation and Interceptor, as well as the ActionContext that has not been introduced yet.
ActionInvocation is the total scheduling of the entire process, which is very similar to the Invocation object in Spring AOP. Many Interceptors are built into Struts2. The most important thing is to save the request parameters and pass the foreground data to the Action member variables.
ActionContext is the global context object that saves these data, and the most important thing is the ValueStack used to save the Action instance.
The so-called global means that ActionContext can be accessed in Action and Result, but it is actually ThreadLocal type. Each request thread will have its own instance of Action and ActionContext.
It can be said that learning Struts2 is mainly about learning:
(1) Let Interceptor and Action cooperate to complete the task.
(2) Save the foreground data into the Action.
(3) Result gets the return data from Action through ValueStack.
3. The differences between Struts2 and Struts1
From the above execution process, we can already see the huge difference between Struts1 and 2.
(1) Where did ActionForm go? Is Action still the same Action?
The most obvious thing is that we can't see the ActionForm object in the entire process, and although the Action is still called this name, it seems to be completely different from the Action in Struts1.
First of all, ActionForm was abandoned, and the data sent from the front desk could be saved to any POJO. The day of saving in ActionForm first and then copying to the Dto object is over. Second, this POJO is actually a member variable in the Action object. This is in Struts1
It is impossible to share an Action instance for all requests in this case. Now Struts2 will create an Action instance for each request, so this works. Third, although this is feasible, it seems that Action, as a model M in MVC, saves data and contains business logic. Is this a bad design? Actually, if you think about it carefully, this design is very convenient, we have already obtained the data.
You can directly operate the Service layer. Action seems to have too many responsibilities, but not many.
(2) How did the front-end Servlet become a Filter?
We know that Struts1 and Spring MVC are both used as entrances through front-end Servlets. Why do Struts2 use Servlet filters?
Because Struts2 is based on the Webwork core, it is completely different from Struts1. Webwork can be said to reduce applications and J2EE
API coupling, such as changing ActionServlet to Servlet's Filter, and direct access to HttpServletRequest/Response.
For example, any POJO can serve as ActionForm, any class can be used as Action without implementing the Action interface, etc.
Therefore, Struts2 also inherits this excellent non-invasive design.
This is somewhat similar to Spring's design ideas. For example, those Ware interfaces do not need to be implemented at all, so as to minimize the coupling between application code and framework. Invasiveness is indeed an important factor to consider when designing a framework.
(3) OGNL between Filter, Action, and Result
The following figure can clearly show how OGNL is integrated into the Struts2 framework.
It is so convenient to access the data in Action using the Struts2 tag in the input page InputForm.html and return to the page ResultPage.jsp
OGNL makes access to the properties of Actions saved in ValueStack as convenient as accessing ValueStack's own properties.
The extensive use of OGNL is a major feature of Struts2. Including the foreground tag passing values to Action, the Result taking values from Action, etc., will use OGNL in large quantities. However, reflection is used a lot in OGNL. I think this is one of the reasons why Struts2 is not as good as Struts1. After all, it takes a certain price to obtain a flexible and low-coupled architecture.
(4) Interceptor's strength is invincible
Another powerful feature in Struts2 is the Interceptor interceptor. Struts2 has built-in a large number of interceptors, which enable a large amount of code to be reused, automating what we previously called trivial tasks, thus enabling Struts2 to reach a high level of separation of attention. This is really a model for the application of AOP ideas in the framework!
Struts2 three data transfer methods
Struts2 provides three ways to save parameters in HTTP requests: JavaBean attributes, JavaBean objects, and ModelDriven objects. Let’s take a look at these three data transfer methods through the most common login example. The page code is very simple. The submission form contains the user name and password. You can get these two parameters in the Action to verify whether the user logs in successfully.
1. JavaBean properties
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head></head> <body> <h1>Login Page</h1> <form action="/cdai/login" method="post"> <div> <label for="username">Name:</label> <input id="username" name="username" type="textfield"/> </div> <div> <label for="password">Password:</label> <input id="password" name="password" type="password"/> </div> <div> <label for="rememberMe"> <input id="rememberMe" name="rememberMe" type="checkbox"/> Remember me</label> <input type="submit" value="Login"></input> </div> </form> </body> </html>
package com.cdai.web.ssh.action; import com.cdai.web.ssh.request.LoginRequest; import com.cdai.web.ssh.service.UserService; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ModelDriven; public class LoginAction implements Action { private String username; private String password; private UserService userService; @Override public String execute() { System.out.println("Login action - " + request); return SUCCESS; } public String getUsername() { return request; } public void setUsername(String username) { this.username = username; } public String getPassword() { return request; } public void setPassword(String Password) { this.Password = Password; } }This method is relatively simple, directly save the parameters in the form to the properties in the Action. When verifying, Action may also need to encapsulate the username and password into Dto to pass it to the Service layer for verification. So why not go one step further and save the username and password directly into Dto.
2. JavaBean Objects
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head></head> <body> <h1>Login Page</h1> <form action="/cdai/login" method="post"> <div> <label for="username">Name:</label> <input id="username" name="request.username" type="textfield"/> </div> <div> <label for="password">Password:</label> <input id="password" name="request.password" type="password"/> </div> <div> <label for="rememberMe"> <input id="rememberMe" name="rememberMe" type="checkbox"/> Remember me</label> <input type="submit" value="Login"></input> </div> </form> </body> </html>
package com.cdai.web.ssh.action; import com.cdai.web.ssh.request.LoginRequest; import com.cdai.web.ssh.service.UserService; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ModelDriven; public class LoginAction implements Action { private LoginRequest request; private UserService userService; @Override public String execute() { System.out.println("Login action - " + request); return SUCCESS; } public LoginRequest getRequest() { return request; } public void setRequest(LoginRequest request) { this.request = request; } } This makes it easy to call the Service layer directly. But there is a small disadvantage that this deepens the depth of the page parameter name, only adding a request to the parameter name
The prefix (the attribute name in the Action) enables Struts2 to correctly save the parameters in the form to the request object through OGNL.
3. ModelDriven object
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head></head> <body> <h1>Login Page</h1> <form action="/cdai/login" method="post"> <div> <label for="username">Name:</label> <input id="username" name="username" type="textfield"/> </div> <div> <label for="password">Password:</label> <input id="password" name="password" type="password"/> </div> <div> <label for="rememberMe"> <input id="rememberMe" name="rememberMe" type="checkbox"/> Remember me</label> <input type="submit" value="Login"></input> </div> </form> </body> </html>
package com.cdai.web.ssh.action; import com.cdai.web.ssh.request.LoginRequest; import com.cdai.web.ssh.service.UserService; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ModelDriven; public class LoginAction implements Action, ModelDriven<LoginRequest> { private LoginRequest request = new LoginRequest(); private UserService userService; @Override public String execute() { System.out.println("Login action - " + request); return SUCCESS; } @Override public LoginRequest getModel() { return request; } } In this way, one more ModelDriven interface is required, and the objects provided by ModelDriven are saved to ValueStack, so that the foreground page can be directly passed
The username and password attribute names define the parameter name of the form.
Which of the three methods should not be generalized? It depends on the specific needs of the project and then decide it yourself!