directory
- Principles and manual implementation of IOC in the Spring series
- Spring series DI principle and manual implementation
- Spring series of AOP principles and manual implementation
- Spring series handwritten annotations and configuration file parsing
The introduction
In the previous chapters we have simply completed a simplified version of Spring that includes container, dependency injection, AOP, and configuration file parsing capabilities. In this section we’ll implement a springMvc of our own.
About the MVC/for SpringMVC
SpringMvc is a Web framework based on the MVC pattern. The springMvc framework is a component that provides the MVC(Model-View-Controller) architecture and for developing flexible and loosely coupled Web applications.
The MVC pattern separates the different parts of the application while providing loose coupling between these elements.
- A Model encapsulates application data, which usually means ordinary beans.
- The View is responsible for rendering model data and generally generates HTML output that the client browser can interpret.
- The Controller is responsible for processing the user request and taking the result of the request and passing it to the view for rendering.
SpringMVC
SpringMVC handles the request flow
First, let’s take a look at what SpringMVC is doing throughout the process of handling HTTP requests.
// The image is from the Internet
From the figure above, we can summarize the springMVC process:
- The client sends the request, which is intercepted by the Web container (Tomcat, etc.), which passes the request to the DispatcherServlet.
- The DispatcherServlet receives the request and sends it to the HandlerMapping to find the Handler for the request. In fact, this process is divided into two in the diagram, because a request URL may have multiple request handlers, such as GET request, POST request, etc., which are handled by different processor objects, so a HandlerAdapter is needed to obtain the corresponding processor object according to different request parameters.
- After obtaining the requested processor object, the request processing flow of the processor is executed. The process flow here is typically the business process we defined in development.
- After the process completes, the returned result is wrapped as a ModelAndView object and returned to the DispatcherServlet.
- The DispatcherServlet parses a ModelAndView to a View through the ViewResolver(View parser).
- Render the page through the View and respond to the user.
This is an HTTP request from the beginning of the complete response by SpringMVC, our SpringMVC is not as complex as the actual, but the corresponding functionality will be implemented.
For SpringMVC analysis
We know that SpringMVC is a Web framework that implements the MVC pattern, so there must be Model, View, and Controller roles. We can also see a very important class in the figure above :DispatcherServlet. Now let’s analyze it separately.
Controller
The Controller is responsible for processing the user request and taking the result of the request and passing it to the view for rendering.
In SpringMVC the Controller is responsible for handling requests sent by DispatcherServlet and wrapping the result of the business process into a Model for the View to use. Mapping requests to corresponding Controllers in SpringMVC gives us two different approaches:
- Instance-level mapping, where each request has a corresponding class instance to handle, similar to Struts2. This method is rarely used in practice. To implement this type you need to implement a Controller interface.
- Method-level mapping, where requests are mapped to bean methods, allows for multiple requests per Controller and makes it easier to keep concurrent requests thread-safe.
Instance level mapping
Consider how to implement instance-level mapping?
In instance level mapping each request corresponds to a different Class, i.e. a URL<==> a Class, so that we can map beanName to the request address.
At the same time our framework needs to provide an entry point for request processing for the consumer to implement the business code. Here we define a Controller interface that provides a processing method.
The interface contains a handlerRequest handler, and all instance level controllers need the Controller interface. Complete the business logic in the handlerRequest method. Since we cannot determine what type of value to return after the business logic is complete, Object is used instead.
So to figure out what the method should return, we first have to figure out what the return value is for, okay? The returned value contains the result of the business process. And returned to the page for page rendering. So it must hold a result value and a specific page to return. Considering that the returned value is not just the result of business processing, and maybe the user needs to set some other value to the page, we define a map type to receive it.
The hasView() method is added because we don’t necessarily return information about the page, such as a json value.
The view’s job is very simple, it just tells the browser the value we return. So the view interface looks like this:
Method level mapping
As anyone who has used SpringMVC knows, the above instance-level mapping approach is rarely used. Instance-level mapping can result in an exceptionally large number of classes in a project once there are too many requests in the project. We usually use method-level mapping.
We don’t need to implement the Controller interface for our method-level mapping here. Here we mimic SpringMVC using @Controller to represent a Controller and @requestMapping to represent different requests.
RequestMethod is an HTTP request type, including GEt,POST,PUT, and so on. It is an enumerated type.
Method mapping is handled similarly to instance mapping except for mapping to methods.
Request distribution
The client sends the request, and after receiving the request, the back end needs to distribute the request to the corresponding processor. From a macro perspective, the request is sent to the DispatcherServlet, and then the DispatcherServlet distributes to different processors. We obviously need to know how the request is distributed to the processor.
Different types of mapping must have different request handlers, such as instance mapping by implementing the Controller interface, processing by interface, and method mapping by annotation. That is, different ways, different mappings, different request handlers.
The simple approach is to define each type of processor to handle and then determine the specific processor by judgment in the DispatcherServlet. This approach is simple, but the problem is that if we want to add another approach, then we need to change the original code, which clearly violates the open and close principle, and will cause trouble for code maintenance. So we wanted a DispatcherServlet that would circumvent this change.
What we need here is one that can call different processors for different requests that are passed in, and that can be easily extended without changing the original code. This is where design patterns come in, of course. What design patterns do you use? Policy mode.
HandlerMapping
We define an interface for request handler mapping. The interface’s job is to get a request specific request handler, and different processing methods implement this interface respectively.
BeanNameUrlHandlerMapping mapping of the processor is instance, RequestMappingHandlerMapping mapping of the processor is method. How do you map requests to HandlerMapping? All we can think of is urls. Here we define a urlMaps to store the mapping between urls and the processor mapper.
HandlerAdapter
Now that we have HandlerMapping, we can get processing objects for a certain type of processing. But we still don’t actually get the actual handler, so we need to get the actual handler on request. Here we define a HandlerAdapter to get the actual handler.
handler(…) This is the specific processing method, which is actually the execution controller, and support is mainly used to determine if it is a processor object.
It’s obvious here that for instance mapping we just need to execute the handlerRequest(…) in the method. Methods, however, are not so simple for method mapping. Different methods represent different requests according to @requestMapping. So we also need a class to represent the different method information so that the request can be retrieved as it comes in.
In the class definition, classRequestMapping specifies the @requestMapping applied to a class, methodRequestMapping specifies the @requestMapping applied to a method, and method specifies the method information applied to a class. match(…) The RequestMappingInfo () method is used to check whether the current request matches the RequestMappingInfo.
Scan the registration
Now that we’re done, we need to think about how to identify our controller and generate RequestMappingInfo information.
The time to create first is always to initialize this information when the project starts, because the information will be immediately available when the request comes in. Where does the act of initializing this information take place? My first instinct was to hand it over to the DispatcherServlet, because it’s intuitively what uses this information, but it’s really the HandlerMapping implementation class that gets the actual handler through requests and RequestMappingInfo, etc. So the initialization information should go to the HandlerMapping implementation class.
It is important to understand that extracting a controller-annotated bean or a class that implements a controller-like interface must be done after the bean is initialized, so we need to provide an interface that gets the Controller type after initialization. You will definitely use ApplicationContext if you get the class already initialized. Let’s change the RequestMapping interface now.
Get the controller type bean in the afterPropertiesSet() method. The previous code we completed provided only methods to get beans by beanName, so here we also need to provide a method to get all of the execution types.
public void afterPropertiesSet() {
String[] beanNameForType = applicationContext.getBeanNameForType(Object.class);
for(String beanName:beanNameForType){
Class type= applicationContext.getType(beanName); // Check whether it is a controller typeif (isHandler(typeDetectHandlerMethod (detectHandlerMethod) {type); }}}Copy the code
DispatcherServlet
Ok, now that we are ready for the controller, we need to implement the DispatcherServlet.
From the name of DispatcherServlet we know that this is a Servlet, our framework is based on the Servlet to complete, SpringMVC framework itself is based on the Servlet. It can also be implemented with other technologies, such as Struts2 based on filters.
Let’s take a look at what DispatcherServlet needs to do:
- Create the ApplicationContext container object
- Get from the container
HandlerMapping
.HandlerAdapter
Object. - Distribute the requests
- The view forward
Anyone familiar with servlets should know that servlets provide a series of lifecycle apis, all of which need to be done at different stages of the Servlet life cycle.
- Initialization and retrieval of container objects
HandlerMapping
.HandlerAdapter
Object in the init (…). To complete. - Request distribution by
service(HttpServletRequest req, HttpServletResponse res)
To complete. destroy()
The post-shutdown processing is complete.
View
Earlier when we defined the controller, we said that the controller returns a ModelAndView object, which determines which page to return and the data to process the result. Now we want this process to be as simple as possible. The user can just provide the name of the View, and the frame will automatically find the corresponding page and render it.
We now need to redefine the ModelAndViewView class.
This allows the user to pass in a name, and the HandlerAdapter generates the ModelAndView from the passed handler, as well as customizing the ModelAndView object.
ViewResolver
This doesn’t mean we’re done, because different views may do different things, such as forwarding directly to one URL, redirecting to another URL, or simply returning json strings. So we also need to define different View parsers to parse ModelAndView into corresponding views.
Here you define a parser that parses the JSP view, and you can define other processors as well.
With the view parser defined, we also need to define several view classes to handle different situations.
The View type can also add other types of processors depending on your needs, such as Freemarker, JSTL, etc. For json processing we also need to define an @responseBody annotation like SpringMVC does. When this annotation is used we convert the return value into a JSON string and return it directly to the client via response.
summary
So that’s pretty much the end of SpringMVC, but overall it’s a little bit of a hassle. The main reason is that there are many contents involved, and this time is busy, so I usually take time to sort out after work. At present, I just finish thinking basically. The code is just a framework; the content has not been populated. So there may be some mistakes in the article, you can point them out if you find them. I’ll do it later when I have time. The code is all here: Spring.
conclusion
This series of articles is to consolidate my Spring learning achievements. Of course, it would be better if I could help you learn Spring. The content of Spring is very complicated, involving a lot of content, this series of articles can only help you to have an initial understanding of the principle of Spring, in the process of looking at the source of Spring is not completely confused. Because of the technical level of the reasons, there may be some mistakes in the article, welcome to point out.