迷你版Spring MVC 實現

迷你版Spring MVC 實現

本文參考自 寫出個人第一個框架:迷你版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 "";

}

編寫核心Servlet

在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方法

編寫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

相關文章
相關標籤/搜索