本文參考自 寫出個人第一個框架:迷你版Spring MVC ,寫這篇文章用於我的學習的記錄。java
首先,創建一個空Maven項目,在java目錄下,創建如下包結構。git
..... |--com.it |-annotation |-dao |-service |-web DispatchServlet
接下來,引入servlet依賴web
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency>
在annotation增長以下註解:spring
@Autowired.javaapi
@Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { String value(); }
@Controller.javatomcat
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { String value() default ""; }
@Repository.javabash
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Repository { String value() default ""; }
@RequestMapping.javamvc
@Documented @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String value(); }
@Service.javaapp
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Service { String value() default ""; }
在Spring MVC中,DispatcherServlet是核心,所以咱們也創建一個本身的DispatcherServlet來接收本身的請求。框架
@WebServlet是什麼?
在Servlet3.0後出現了基於註解的Servlet,配置Servlet支持註解方式。
爲了方便,我這裏就經過初始化參數直接將須要掃描的基包路徑傳入。
DispatcherServlet.java
@WebServlet(urlPatterns = "/*", loadOnStartup = 0, initParams = {@WebInitParam(name = "base-package", value = "com.it")}) public class DispatchServlet extends HttpServlet { private static final String EMPTY = ""; /** 掃描的基礎包*/ private String basePackage = EMPTY; /** 掃描的基礎包下面的類的全類名*/ private List<String> packagesName = new ArrayList<>(); /** key:註解裏的值(默認爲類名第一個字母小寫) value :類的實例化對象*/ private Map<String, Object> instanceMap = new HashMap<>(); /** key:類的全類名 value:註解裏的值(默認爲類名第一個字母小寫)*/ private Map<String, String> nameMap = new HashMap<>(); /** key:請求路徑 value: 所調用的方法*/ private Map<String, Method> urlMethodMap = new HashMap<>(); /** key:controller裏的方法示例 value: 當前類的全類名*/ private Map<Method, String> methodPackageMap = new HashMap<>(); }
接下來,須要在serlvet初始化的時候,去掃描咱們註解標識的類的信息,整體步驟以下:
1)掃描制定包下全部的類,進行初始化,記錄類的全類名。
2)掃描全部標註@Controller/@Service/@Repository的註解類,而且記錄全類名與實例的映射關係。
3)對於有@Autowired註解的字段,咱們須要爲其注入相應類的實例,爲了方便,在這裏統一用類的名稱去獲取,固然在spring 中能夠經過類型等種種手段去注入。這裏全部的類名默認轉換成了 類名首字母小寫的形式。
4)對於全部@RequestMapping註解的形式,須要將請求路徑和相應的方法作關聯記錄,這樣咱們能夠經過請求路徑映射到咱們的controller方法上。
Servlet初始化
@Override public void init(ServletConfig config) throws ServletException { basePackage = config.getInitParameter("base-package"); try { scanBasePackage(basePackage); instance(packagesName); ioc(); handlerUrlMethod(); } catch (Exception e) { e.printStackTrace(); System.out.println("框架加載失敗"); } }
第一步 ,包名掃描
private void scanBasePackage(String basePackage) { Optional<URL> resource = Optional.ofNullable(this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"))); String path = resource.map(URL::getPath).orElse(""); File basePackageFile = new File(path); Optional<File[]> files = Optional.ofNullable(basePackageFile.listFiles()); files.ifPresent(file -> { for (File temp : file) { if (temp.isDirectory()) { scanBasePackage(basePackage + "." + temp.getName()); } if (temp.isFile()) { packagesName.add(basePackage + "." + temp.getName().split("\\.")[0]); } } }); }
第二步,實例化類
private void instance(List<String> packagesNames) { if (packagesNames.isEmpty()) { return; } packagesNames.forEach(packageName -> { try { Class clazz = Class.forName(packageName); String subName = clazz.getName().substring(clazz.getName().lastIndexOf(".")).replaceAll("\\.", EMPTY); String clazzName = subName.substring(0, 1).toLowerCase() + subName.substring(1); if (clazz.isAnnotationPresent(Controller.class)) { Controller controller = (Controller) clazz.getAnnotation(Controller.class); String controllerName = controller.value(); if (EMPTY.equals(controllerName)) { controllerName = clazzName; } instanceMap.put(controllerName, clazz.newInstance()); nameMap.put(packageName, controllerName); } if (clazz.isAnnotationPresent(Service.class)) { Service service = (Service) clazz.getAnnotation(Service.class); String serviceName = service.value(); if (EMPTY.equals(serviceName)) { serviceName = clazzName; } instanceMap.put(serviceName, clazz.newInstance()); nameMap.put(packageName, serviceName); } if (clazz.isAnnotationPresent(Repository.class)) { Repository repository = (Repository) clazz.getAnnotation(Repository.class); String respositoryName = repository.value(); if (EMPTY.equals(respositoryName)) { respositoryName = clazzName; } instanceMap.put(respositoryName, clazz.newInstance()); nameMap.put(packageName, respositoryName); } } catch (Exception e) { e.printStackTrace(); System.out.println("類加載失敗"); } }); }
第三步,注入類的實例
private void ioc() throws IllegalAccessException { for (Map.Entry<String, Object> instance : instanceMap.entrySet()) { Field[] declaredFields = instance.getValue().getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { if (declaredField.isAnnotationPresent(Autowired.class)) { String value = declaredField.getAnnotation(Autowired.class).value(); declaredField.setAccessible(true); declaredField.set(instance.getValue(), instanceMap.get(value)); } } } }
第四步,處理請求路徑與方法的映射
private void handlerUrlMethod() { if (packagesName.isEmpty()) { return; } packagesName.forEach(packageName -> { try { Class clazz = Class.forName(packageName); if (clazz.isAnnotationPresent(Controller.class)) { Method[] methods = clazz.getMethods(); StringBuffer baseUrl = new StringBuffer(); if (clazz.isAnnotationPresent(RequestMapping.class)) { RequestMapping controllerClazzMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class); baseUrl.append(controllerClazzMapping.value()); } for (Method method : methods) { if (method.isAnnotationPresent(RequestMapping.class)) { RequestMapping methodMapping = method.getAnnotation(RequestMapping.class); baseUrl.append(methodMapping.value()); urlMethodMap.put(baseUrl.toString(), method); methodPackageMap.put(method, packageName); } } } } catch (Exception e) { e.printStackTrace(); System.out.println("獲取映射失敗"); } }); }
編寫GET/POST方法,使請求映射到咱們的Controller上
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uri = req.getRequestURI(); String contextPath = req.getContextPath(); String path = uri.replace(contextPath, EMPTY); Optional<Method> method = Optional.ofNullable(urlMethodMap.get(path)); method.ifPresent(m -> { String packageName = methodPackageMap.get(m); String controllerName = nameMap.get(packageName); Object controllerObj = instanceMap.get(controllerName); m.setAccessible(true); try { m.invoke(controllerObj); } catch (Exception e) { e.printStackTrace(); } }); }
UserDao.java
@Repository public class UserDao { public void insert(){ System.out.println("insert user:"); } }
UserService.java
@Service public class UserService { @Autowired("userDao") private UserDao userDao; public void insert() { userDao.insert(); } }
UserController.java
@Controller @RequestMapping("/user") public class UserController { @Autowired("userService") private UserService userService; @RequestMapping("/insert") public void insert() { userService.insert(); } }
編寫完成後,在tomcat啓動,訪問 IP:PORT + /user/insert 後,能夠在控制檯看到 insert user:
的輸出,說明咱們的迷你版Spring MVC能夠正常工做啦。
代碼參考:mymvc