手寫一個SpringMVC

網上不少關於springmvc的分析,但不多有手動實現的,這裏經過一個簡單的demo實現mvc基本功能,僅供學習參考。java

咱們先簡單瞭解下springmvc請求流程,以下圖:
image.png
從圖上得知,最早處理請求的是dispatcherServlet,它接受請求並查詢獲得攔截器鏈HandlerExecutionChain,HandlerAdapter通過適配器調用具體的處理器(Controller).web

查看源碼能夠到spring-webmvc.jar中,org.springframework.web.servlet/DispatcherServlet.class,其中方法doDispatch()完成了一個請求到返回數據的完整操做.spring

下面咱們開始動手了,先建立一個javaweb工程,寫一個Servlet,以下:瀏覽器

@WebServlet(urlPatterns = "/*", loadOnStartup = 1)
public class DispatcherServlet extends HttpServlet {

    private List<String> clzList = new ArrayList<>();
    private Map<String, Object> beansMap = new HashMap<>();
    private Map<String, Object> urlMapping = new HashMap<>();

    @Override
    public void init() throws ServletException {
        try {
            //掃描配置  這裏是包名
            scanPackages("com.iti.smvc");
            //實例化對象
            doInstance();
            //創建對象之間的依賴ioc
            ioc();
            //創建url到controller的映射
            doMapping();

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

注:servlet3.0再也不須要web.xml, 這裏將成員變量寫入servlet並非很好的實現,會致使線程不安全
這個servlet要作一些初始化的工做,如:
1.掃描包名,將class對象裝入clzList列表
2.遍歷clzList列表,實例化有Controller和Service標註的類
3.依賴注入,將service注入到controller
4.創建url與controller中方法url的mapping關係安全

咱們依次來實現他們:
下面的是掃描配置方法mvc

private void scanPackages(String packageUrl) {
        String fileUrl = getClass().getClassLoader().getResource("/"+packageUrl.replaceAll("\\.", "/")).getFile();
        File scanfile = new File(fileUrl);
        String[] fileList = scanfile.list();
        for (String fileName: fileList) {
            File file = new File(fileUrl+fileName);
            if (file.isDirectory()) {
                scanPackages(packageUrl + "." + fileName);;
            } else {
                clzList.add(packageUrl + "." + fileName.replace(".class", ""));
            }
        }
    }

接着是對象實例化:app

private void doInstance() throws Exception{
        if (clzList.size()>0) {
            for (String clzName : clzList) {
                Class<?> clzClass = Class.forName(clzName);
                if (clzClass.isAnnotationPresent(Controller.class)) {
                    //for controller
                    RequestMapping rm = clzClass.getAnnotation(RequestMapping.class);
                    beansMap.put(rm.value(), clzClass.newInstance());
                } else if (clzClass.isAnnotationPresent(Service.class)) {
                    Service service = clzClass.getAnnotation(Service.class);
                    beansMap.put(service.value(), clzClass.newInstance());
                }
            }
        }
    }

接着是依賴注入,實際上是反射:ide

private void ioc() throws Exception{
        if (beansMap.size()>0) {
            for (Map.Entry<String, Object> entry : beansMap.entrySet()) {
                Field[] fields = entry.getValue().getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(Autowired.class)) {
                        Qualifier anno = field.getAnnotation(Qualifier.class);
                        field.set(entry.getValue(), beansMap.get(anno.value()));
                    }
                }
            }
        }
    }

最後是創建請求url與controller的mapping關係,以下:學習

private void doMapping() {
        if (beansMap.size()>0) {
            for (Map.Entry<String, Object> entry: beansMap.entrySet()) {
                Class<? extends Object> obj = entry.getValue().getClass();
                if (obj.isAnnotationPresent(Controller.class)){
                    RequestMapping rm = obj.getAnnotation(RequestMapping.class);
                    Method[] methods = obj.getMethods();
                    for (Method method: methods) {
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping anno = method.getAnnotation(RequestMapping.class);
                            urlMapping.put(rm.value() + anno.value(), method);
                        }
                    }
                }
            }
        }
    }

還要把註解建立下:測試

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Qualifier {
    String value() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

此時就能夠啓動服務完成初始化工做了
下面咱們要建立一個sevice方法來接收url請求

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI(); 
        String contextPath = req.getContextPath(); 
        //獲得請求地址
        String path = requestURI.replace(contextPath, "");
        Method method = (Method)urlMapping.get(path);
        if (method != null) {
            try {
                Object obj = method.invoke(beansMap.get("/"+path.split("/")[1]));
                resp.getOutputStream().write(obj.toString().getBytes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

上面是獲得請求url,而後從urlMapping獲得要調用的method,再經過反射調用,最後經過outputStream輸出到瀏覽器上。

下面編寫一個controller和service測試下
controller代碼以下:

@Controller
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    @Qualifier("helloservice")
    Helloservice helloservice;

    @RequestMapping("/sayHello")
    public String sayHello() {
        //System.out.println(helloservice.hello());
        return "controller";
    }
}

service代碼以下:

@Service("helloservice")
public class Helloservice {
    public String hello() {
        return "hello";
    }
}

請求url:http://localhost:8080/hello/sayHello學習交流,歡迎加羣:64691032

相關文章
相關標籤/搜索