1. 程式人生 > >模仿寫一個簡單的mvc框架

模仿寫一個簡單的mvc框架

手寫一個簡單的mvc框架

前言

  • 為了更好的理解springmvc,前段時間寫了一個簡單的mvc,借鑑了網上找到的一些程式碼不少都是有bug的,今天把程式碼分享出來,希望能夠幫助和我一樣需要這方面學習的人。
  • 在期間遇到的一些坑我會在其他篇部落格上闡述。

github連結

專案結構

  • 在這裡插入圖片描述
    在這裡插入圖片描述

程式碼

  • 各個註解類
package com.mymvc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface MyAutowired {
	 /**
     * 進行依賴注入的時候尋找filed
     * @return
     */
    String value() default "";

}

package com.mymvc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface MyController {
	 /**
     * 表示給controller註冊別名
     * @return
     */
    String value() default "";

}

package com.mymvc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
    /**
     * 表示訪問該方法的url
     * @return
     */
    String value() default "";

}

package com.mymvc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
    /**
     * 表示引數的別名,必填
     * @return
     */
    String value();

}

package com.mymvc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyResponsebody {
	
	String value() default "";

}

package com.mymvc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface MyService {
	 /**
     * 表示給service註冊別名
     * @return
     */
    String value() default "";

}

  • controller
package com.mymvc.core.controller;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSONObject;
import com.mymvc.annotation.MyAutowired;
import com.mymvc.annotation.MyController;
import com.mymvc.annotation.MyRequestMapping;
import com.mymvc.annotation.MyRequestParam;
import com.mymvc.annotation.MyResponsebody;
import com.mymvc.core.pojo.User;
import com.mymvc.core.serviceImpl.UserServiceImpl;

@MyController
@MyRequestMapping("/test")
public class TestController {
	
	@MyAutowired("user")
	public UserServiceImpl userServiceImpl;

   

    
     
     @MyRequestMapping("/string")
     //@MyResponsebody
     public String stringTest(HttpServletRequest request, HttpServletResponse response){
         Map<String, String> map = new HashMap<String, String>();
         map.put("123", "454");

    	 return "MyHtml";
     }
     @MyRequestMapping("/json")
     @MyResponsebody
     public  Map<String, Object> jsonTest(HttpServletRequest request, HttpServletResponse response){
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("123", "454");
         User user = new User();
         map.put("user", user);

        
    	 return map;
     }
     
     @MyRequestMapping("/userservice")
     @MyResponsebody
     public  User userservice(HttpServletRequest request, HttpServletResponse response){
    	 User user = userServiceImpl.getUser();
        

        
        return user;
     }
     
     @MyRequestMapping("param")
     @MyResponsebody
     public Map paramtest(@MyRequestParam("d")String b, Integer a, Integer c,HttpServletRequest request){
    	 Map<String ,Object> map = new HashMap<String, Object>();
    	 map.put("a", a);
    	 map.put("b", b);
    	 map.put("c", c);
    	 
    	 return map;
     }
     
     
}

  • pojo
package com.mymvc.core.pojo;

public class User {
	
	public String name = "1111";
	
	public String age = "111";

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAge() {
		return age;
	}

	public void setAge(String age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
	
	

}

  • service
package com.mymvc.core.service;

import com.mymvc.core.pojo.User;

public interface UserService {
	
	public User getUser();

}

package com.mymvc.core.serviceImpl;

import com.mymvc.annotation.MyService;
import com.mymvc.core.pojo.User;
import com.mymvc.core.service.UserService;

@MyService("user")
public class UserServiceImpl implements UserService{
	

	@Override
	public User getUser(){
		User user = new User();
		user.setAge("11");
		user.setName("11");
		return user;
	}
	
}

  • servlet
package com.mymvc.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.PrefixFileFilter;

import com.alibaba.fastjson.JSONObject;
import com.mymvc.annotation.MyAutowired;
import com.mymvc.annotation.MyController;
import com.mymvc.annotation.MyRequestMapping;
import com.mymvc.annotation.MyRequestParam;
import com.mymvc.annotation.MyResponsebody;
import com.mymvc.annotation.MyService;
import com.mymvc.core.controller.TestController;
import com.mymvc.core.serviceImpl.UserServiceImpl;

public class MyDispatcherServlet extends HttpServlet{
	
	 //放類名
    private List<String> classNames = new ArrayList<String>();

    //spring的ioc容器
    private Map<String, Object> ioc = new HashMap<String, Object>();
    
    //將掃描帶有特定註解的方法放在該map中
    private Map<String, Method> handlerMapping = new  HashMap<String, Method>();

    //將掃描帶有contoller註解的類放在該map中
    private Map<String, Object> controllerMap  =new HashMap<String, Object>();

    //尋找該專案靜態資源時,使用者設定的字首
    private String suffix ;
    
    //尋找該專案靜態資源時,使用者設定的字尾
    private String prefix ;
    
    //需要掃描的包名(controllr)
    private String packages ;
    
    @Override
    public void init(ServletConfig config) throws ServletException {

    	//1.初始化基本資訊
    	getBasicConfig(config);
    	
        //2.初始化所有相關聯的類,掃描使用者設定的包下面所有的類
        scanPackages(this.packages);

        //3.拿到掃描到的類,通過反射機制,例項化,並且放到ioc容器中(k-v  beanName-bean) beanName預設是首字母小寫
        doInstance();

        //4.進行依賴注入
        doIoc();
        
        //5.初始化HandlerMapping(將url和method對應上)
        initHandlerMapping();
        
        

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            //處理請求
            doDispatch(req,resp);
        } catch (Exception e) {
            resp.getWriter().write("500!! Server Exception");
        }

    }

  
    
      
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    	 //判斷是否url對映的方法是否為空
    	if(handlerMapping.isEmpty()){
             return;
         }
    	
    	 String url =req.getRequestURI();
         String contextPath = req.getContextPath();

         url=url.replace(contextPath, "").replaceAll("/+", "/");

         if(!this.handlerMapping.containsKey(url)){
             resp.getWriter().write("404 NOT FOUND!");
             return;
         }

         Method method =this.handlerMapping.get(url);
         
         //獲取方法的引數列表
         Class<?>[] parameterTypes = method.getParameterTypes();

         //獲取請求的引數
         Map<String, String[]> parameterMap = req.getParameterMap();

         //儲存引數值
         Object [] paramValues= new Object[parameterTypes.length];
         
         Parameter[] parameters = method.getParameters();
         
          
        	 
         for (int i = 0; i<parameterTypes.length; i++){  
        	 String requestParam = parameterTypes[i].getSimpleName(); 
        	 
        	 if (requestParam.equals("HttpServletRequest")){  
                 //引數型別已明確,這邊強轉型別  
                 paramValues[i]=req;
                 System.out.println("request :" + i);
                 continue;  
             }  
             if (requestParam.equals("HttpServletResponse")){  
                 paramValues[i]=resp;
                 System.out.println("response :" + i);
                 continue;  
             }
             for(Map.Entry<String, String[]> entry : parameterMap.entrySet()){
            	 
            	 if(parameters[i].isAnnotationPresent(MyRequestParam.class)){
            		 String annotationValue = ((MyRequestParam)parameters[i].getAnnotation(MyRequestParam.class)).value();
            	    if(annotationValue.equals(entry.getKey())){
            	    	
            	    	String value =Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
               		    paramValues[i] = value;
               		    if(parameters[i].getType().toString().equals("class java.lang.Integer"))
               		    	paramValues[i] =Integer.parseInt(value) ;
            	       continue;
            	    }
            	 }
            	 
            	 
            	 if(parameters[i].getName().equals(entry.getKey())){
            		 String value =Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
            		 paramValues[i] = value;
            		 if(parameters[i].getType().toString().equals("class java.lang.Integer"))
            		    	paramValues[i] =Integer.parseInt(value) ;
            		 continue;
            	 }
            	
            		 
             }
             
        	 
         }
         
         for(Object prm : paramValues){
         }
         
         try {
            Object object = method.invoke(this.controllerMap.get(url), paramValues);//第一個引數是method所對應的例項 在ioc容器中
            if(method.isAnnotationPresent(MyResponsebody.class)){
         	   if(!(null == object)){
         		   
             	   resp.getWriter().write( JSONObject.toJSONString(object) );  
                }
            }else {
         	   Type type =  method.getGenericReturnType();
         	   if("class java.lang.String".equals(type.toString())){
         		   outputTheFile(req,resp,object.toString());        		   
         	   }
            
            }
            
         } catch (Exception e) {
             e.printStackTrace();
         }
    	
    }
    
    private void getBasicConfig(ServletConfig config){
    	
    	this.packages = config.getInitParameter("packagesToScan");
    	this.suffix = config.getInitParameter("suffix");
    	this.prefix = config.getInitParameter("prefix");
    	
    }

  
    private void scanPackages(String packageName) {
        //把所有的.替換成/
        URL url  =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if(file.isDirectory()){
                //遞迴讀取包
            	scanPackages(packageName+"."+file.getName());
            }else{
                String className =packageName +"." +file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }   
        for (String className : classNames) {
            try {
                //通過反射機制,將controllr註解和service註解的類獲得,放在map上
                Class<?> clazz =Class.forName(className);
               if(clazz.isAnnotationPresent(MyController.class)){
                    ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
               }else if(clazz.isAnnotationPresent(MyService.class)){
                	String serviceName = clazz.getAnnotation(MyService.class).value();
                	if(serviceName.equals("") || serviceName == null){
                		ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
                	}else {
                		ioc.put(serviceName,clazz.newInstance());
                	}
                	
                }else{
                
                    continue;
                }

            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    private void initHandlerMapping(){
        if(ioc.isEmpty()){
            return;
        }
        try {
            for (Entry<String, Object> entry: ioc.entrySet()) {
                Class<? extends Object> clazz = entry.getValue().getClass();
                if(!clazz.isAnnotationPresent(MyController.class)){
                    continue;
                }

                //拼url時,是controller頭的url拼上方法上的url
                String baseUrl ="";
                if(clazz.isAnnotationPresent(MyRequestMapping.class)){
                    MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl=annotation.value();
                }
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(!method.isAnnotationPresent(MyRequestMapping.class)){
                        continue;
                    }
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String url = annotation.value();

                    url =(baseUrl+"/"+url).replaceAll("/+", "/");
                    handlerMapping.put(url,method);
                    controllerMap.put(url,entry.getValue());
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    
    private void doIoc(){
    	if(ioc.isEmpty()) return ;
    	for(Entry<String, Object> entry : ioc.entrySet()){
    		Field[] fields = entry.getValue().getClass().getDeclaredFields();
    		for(Field field : fields){
    			 field.setAccessible(true);
    			if(field.isAnnotationPresent(MyAutowired.class)){
    				String autowiredValueName = field.getAnnotation(MyAutowired.class).value();
    				if(autowiredValueName == null || autowiredValueName.equals("")){
    					autowiredValueName = toLowerFirstWord(field.getType().getSimpleName());
    				}
    				field.setAccessible(true);
    				try {
    					field.set(entry.getValue(), ioc.get(autowiredValueName));
    					
					} catch (IllegalArgumentException e) {
						// TODO: handle exception
						e.printStackTrace();
					}catch (IllegalAccessException  e) {
						// TODO: handle exception
						e.printStackTrace();
					}
    			}
    		}
    		
    	}
    	
    }

    /**
     * 把字串的首字母小寫
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name){
        char[] charArray = name.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }
    
    /**
     * 將檔案輸出到前端
     */
    private void outputTheFile(HttpServletRequest request,HttpServletResponse response,
    		String filePath){
    	
    	String uri =request.getRequestURI();
    	String dir = request.getSession(true).getServletContext().getRealPath("/");
    	dir = dir + prefix + "/" +filePath + suffix;
    	response.setContentType("text/html; charset=UTF-8");
    	try {
			InputStream inputStream = new FileInputStream(dir);
			String html = IOUtils.toString(inputStream,"UTF-8");
			PrintWriter out = response.getWriter();
			out.println(html);
			out.flush();
			out.close();
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}catch (IOException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
    	
    	
    }
    
}

  • web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <servlet>
        <servlet-name>MySpringMVC</servlet-name>
        <servlet-class>com.mymvc.servlet.MyDispatcherServlet</servlet-class>
        <init-param>
            <param-name>packagesToScan</param-name>
            <param-value>com.mymvc.core</param-value>
        </init-param>
        <init-param>
            <param-name>suffix</param-name>
            <param-value>.html</param-value>
        </init-param>
        <init-param>
            <param-name>prefix</param-name>
            <param-value>html</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>MySpringMVC</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>


  • pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>mymvc</groupId>
  <artifactId>mymvc</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>mymvc</name>
  <description/>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <maven.compiler.source>1.8</maven.compiler.source>
   <maven.compiler.target>1.8</maven.compiler.target>
   <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>bean-validator</artifactId>
      <version>3.0-JBoss-4.0.2</version>
      <scope>provided</scope>
    </dependency>
    
	<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
   </dependency>
   
   
	<dependency>  
	    <groupId>com.alibaba</groupId>  
	    <artifactId>fastjson</artifactId>  
	    <version>1.2.41</version>  
	</dependency> 


    
    <dependency>
        <groupId>javax.servlet</groupId> 
      <artifactId>javax.servlet-api</artifactId> 
      <version>3.0.1</version> 
      <scope>provided</scope>
   </dependency>
    
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.enterprise.deploy</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.jms</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.management.j2ee</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.resource</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.security.auth.message</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.security.jacc</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.servlet</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.servlet.jsp</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.servlet.jsp.jstl</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api-osgi</artifactId>
      <version>2.2.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.ws.rs</groupId>
      <artifactId>jsr311-api</artifactId>
      <version>1.1.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish.web</groupId>
      <artifactId>jstl-impl</artifactId>
      <version>1.2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.mail</groupId>
      <artifactId>mail</artifactId>
      <version>1.4.3</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.xml</groupId>
      <artifactId>webservices-api-osgi</artifactId>
      <version>2.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.weld</groupId>
      <artifactId>weld-osgi-bundle</artifactId>
      <version>1.0.1-SP3</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish.web</groupId>
      <artifactId>javax.servlet.jsp.jstl</artifactId>
      <version>1.2.1</version>
    </dependency>
  </dependencies>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <resources>
      <resource>
        <directory>src</directory>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <warSourceDirectory>${basedir}/WebRoot</warSourceDirectory>
          <version>3.0</version>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

執行結果

在這裡插入圖片描述