Abstract
Spring MVC(Model-View-Controller) is widely used in current Java web development, even in the "frontend backend separation" era. It not only implements all the crucial Spring features like the inversion of control and the dependency injection but combines the service of the frontend and backend. Therefore, it is vital for us to look deep into the principle of Spring MVC, to understand its logic and code. This tutorial will introduce the principle of Spring MVC by using simply examples and images. Let's start it!
The DispatcherServlet
The most critical component of Spring MVC is the DispathcerServlet. All the framework and logic of the Spring MVC are built around the DispatcherServlet. Here we created a simple POJO java type DispatherServlet.
public class DispatcherServlet extends ViewBaseServlet{
private Map<String,Object> beanMap = new HashMap<>();
public DispatcherServlet(){
}
public void init() throws ServletException {
super.init();
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.Create DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2. Create DocumentBuilder Object
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
Document document = documentBuilder.parse(inputStream);
//3. Obtain all the bean nodes from applicationContext.xml
NodeList beanNodeList = document.getElementsByTagName("bean");
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
Element beanElement = (Element)beanNode ;
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
Class controllerBeanClass = Class.forName(className);
Object beanObj = controllerBeanClass.newInstance() ;
beanMap.put(beanId , beanObj) ;
}
}
...catch exceptions
The basic logic of the Dispatcher Servlet is first to initialize a hashmap to store all the nodes and responded controller classes. The information of each controller's name and class are edited in the applicationContext.xml.
<?xml version="1.0" encoding="utf-8"?>
<beans>
<!-- For example, the information of this controller will be key(fruit) and
value(FruitController)-->
<bean id="fruit" class="com.banana.fruit.controllers.FruitController"/>
</beans>
We created a sample controller bean called "fruit" for this tutorial.I hide the details of the repository layer since it is not crucial in this tutorial.
public class FruitController {
private FruitDAO fruitDAO = new FruitDAOImpl();
private String update(Integer fid , String fname , Integer price , Integer fcount , String remark ){
fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark ));
return "redirect:fruit.do";
}
Most Crucial Part
Some readers may wonder what the function of the hashmap we created above is. In this section, we will discuss the service method of DispactherServlet.
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//set character encoding
request.setCharacterEncoding("UTF-8");
//if URL is: http://localhost:8080/pro15/hello.do
//servletPath is: /hello.do
//so the first part is to get the servletPath string
String servletPath = request.getServletPath();
servletPath = servletPath.substring(1);
int lastDotIndex = servletPath.lastIndexOf(".do") ;
servletPath = servletPath.substring(0,lastDotIndex);
//use the right controller to deal with this request
Object controllerBeanObj = beanMap.get(servletPath);
String operate = request.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index" ;
}
//this part is to use the method including parameters
try {
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
for(Method method : methods){
if(operate.equals(method.getName())){
Parameter[] parameters = method.getParameters();
Object[] parameterValues = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String parameterName = parameter.getName() ;
if("request".equals(parameterName)){
parameterValues[i] = request ;
}else if("response".equals(parameterName)){
parameterValues[i] = response ;
}else if("session".equals(parameterName)){
parameterValues[i] = request.getSession() ;
}else{
String parameterValue = request.getParameter(parameterName);
String typeName = parameter.getType().getName();
Object parameterObj = parameterValue ;
if(parameterObj!=null) {
if ("java.lang.Integer".equals(typeName)) {
parameterObj = Integer.parseInt(parameterValue);
}
}
parameterValues[i] = parameterObj ;
}
}
//2.controller method invoking
method.setAccessible(true);
Object returnObj = method.invoke(controllerBeanObj,parameterValues);
//3. deal with the String return
String methodReturnStr = (String)returnObj ;
if(methodReturnStr.startsWith("redirect:")){
String redirectStr = methodReturnStr.substring("redirect:".length());
response.sendRedirect(redirectStr);
}else{
super.processTemplate(methodReturnStr,request,response);
}
}
}
Here is the result of the service example. Once a request is coming to the dispatcher servlet, the service method will first obtain the type of method that will be invoked. Then, the DispatcherServlet will configure it and get the right controller from the hashmap. Besides, the service method will also choose to create a servlet instance by using reflection and invoke the method according to the request. Finally, there is also a method at the end of the service section to configure the string result.