手写SpringMVC

环境描述

idea

java 8

1. POM文件




  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4.0.0

  org.feng
  hand-springmvc
  1.0-SNAPSHOT
  war

  hand-springmvc Maven Webapp
  
  Example Domain

  
    UTF-8
    1.8
    1.8
  

  
    
    
      junit
      junit
      4.11
      test
    

    
    
      org.slf4j
      slf4j-log4j12
      1.7.25
    
    
      log4j
      log4j
      1.2.17
    

    
    
      javax.servlet
      javax.servlet-api
      3.0.1
    
  

  
    hand-springmvc
    
    
  

 

2. log4j.properties

log4j.rootLogger=INFO, console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %-60c %x - %m%n

 

3. 项目目录

核心内容是注解+servlet

手写SpringMVC_第1张图片

注解类

org.feng.annotation.Autowired

package org.feng.annotation;

import java.lang.annotation.*;

/**
 * Created by Feng on 2019/12/16 17:58
 * CurrentProject's name is hand-springmvc
 * Autowired 用在变量上
 * @author Feng
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}

 

org.feng.annotation.Controller

package org.feng.annotation;

import java.lang.annotation.*;

/**
 * Created by Feng on 2019/12/16 18:01
 * CurrentProject's name is hand-springmvc
 * Controller 用在类上
 * @author Feng
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}

 

org.feng.annotation.RequestMapping

package org.feng.annotation;

import java.lang.annotation.*;

/**
 * Created by Feng on 2019/12/16 18:02
 * CurrentProject's name is hand-springmvc
 * RequestMapping可以用在类、方法上
 * @author Feng
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}

 

org.feng.annotation.RequestParam

package org.feng.annotation;

import java.lang.annotation.*;

/**
 * Created by Feng on 2019/12/16 18:06
 * CurrentProject's name is hand-springmvc
 * RequestParam 用在参数上
 * @author Feng
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}

 

org.feng.annotation.Service

package org.feng.annotation;

import java.lang.annotation.*;

/**
 * Created by Feng on 2019/12/16 18:05
 * CurrentProject's name is hand-springmvc
 * Service 用在类上
 * @author Feng
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

 

核心控制器

package org.feng.servlet;
import org.feng.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Feng on 2019/12/16 17:58
 * CurrentProject's name is hand-springmvc
* 核心控制器: *
    实现思路 *
  • 先扫描基础包,获取 {@code class} 文件的路径;其实是为了获取完整类名
  • *
  • 根据上边的完整类名以及判断是否有指定创建实例(通过有无注解和注解的类型)并保存实例到 {@code map} 中
  • *
  • 依赖注入变量,从实例获得类对象,然后解析 {@code Field} 并赋值给标注了{@link Autowired}的{@code Field}
  • *
  • 获取方法上的参数,通过{@link HttpServletRequest}获取
  • *
*
@author Feng */ public class DispatcherServlet extends HttpServlet { /** * 扫描包:基本的包,扫描该路径下的所有类 */ private static final String BASE_PACKAGE = "org.feng"; private static final String WAR_NAME = "/hand_springmvc"; /**日志*/ private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherServlet.class); /** * 保存class文件的路径 */ private List classPathList = new ArrayList<>(); /** * IOC容器:存放对象;使用{@link ConcurrentHashMap}保证线程安全 */ private Map beans = new ConcurrentHashMap<>(16); /** * 存放方法映射:使用{@link ConcurrentHashMap}保证线程安全
* 用于存储方法
* {
@code key = classpath + methodPath; value = method} */ private Map handlerMap = new ConcurrentHashMap<>(16); /** * 初始化数据: *
    *
  • 扫描所有的类
  • *
  • 创建实例并存储进 {@code beans}
  • *
  • 依赖注入:使用 {@code Autowired}
  • *
  • 拼接请求地址初始化、方法映射
  • *
*/ @Override public void init() { LOGGER.info("starting scan package into classpath list"); scanPackage(BASE_PACKAGE); LOGGER.info("scan package end"); LOGGER.info("classPathList:"+classPathList); LOGGER.info("starting create instance into bean map"); createInstance(); LOGGER.info("create instance end"); LOGGER.info("beans:" + beans); LOGGER.info("starting autowired field"); autowiredField(); LOGGER.info("autowired field end"); LOGGER.info("starting mapping to url"); urlMapping(); LOGGER.info("mapping to url end"); LOGGER.info("handler mapping:" + handlerMap); } /** * 方法映射 */ private void urlMapping() { beans.forEach((key, value) -> { Class clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String classPath = requestMapping.value(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if(method.isAnnotationPresent(RequestMapping.class)){ RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class); String methodPath = requestMapping1.value(); handlerMap.put(classPath + methodPath, method); } } } }); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { this.doPost(req, resp); } /** * 解析调用方法后的返回值:当为String类型时,得到其是转发还是重定向; * @param invokeReturn invoke方法时得到的返回值 * @param req 请求对象 * @param resp 响应对象 */ private void forwardOrRedirect(Object invokeReturn, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { final String forward = "forward:"; final String redirect = "redirect:"; // 当反向调用方法有返回值 if(invokeReturn != null){ // 返回值是字符串:解析字符串 if(invokeReturn.getClass() == String.class){ req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); String returnStr = invokeReturn.toString(); if(returnStr.startsWith(forward)){ returnStr = returnStr.substring(8); LOGGER.info(forward + returnStr); req.getRequestDispatcher(WAR_NAME + returnStr).forward(req, resp); } else if(returnStr.startsWith(redirect)){ returnStr = returnStr.substring(9); LOGGER.info(redirect + returnStr); resp.sendRedirect(WAR_NAME + returnStr); } else { LOGGER.info(redirect + returnStr); resp.sendRedirect(returnStr); } } } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { // 请求的地址 String uri = req.getRequestURI(); uri = uri.replace(WAR_NAME, ""); int index = uri.indexOf("/", 1); String controllerUrl = uri.substring(0, index); Method method = handlerMap.get(uri); LOGGER.info("get method " + method); beans.forEach((key, value) -> { Class clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String valueTemp = requestMapping.value(); if(controllerUrl.equals(valueTemp)){ try { LOGGER.info("invoking " + controllerUrl + "." + value); Object invokeReturn = method.invoke(value, getArgs(req, resp, method)); // 控制:转发或重定向 forwardOrRedirect(invokeReturn, req, resp); } catch (IllegalAccessException | InvocationTargetException e) { LOGGER.error("invoke error in " + controllerUrl); } catch (ServletException | IOException e) { e.printStackTrace(); } } } }); } /** * 解析标注有{@link RequestParam}的方法参数,并赋值 * @param req 请求对象 * @param resp 响应对象 * @param method 方法对象 * @return 赋值后的参数 */ private Object[] getArgs(HttpServletRequest req, HttpServletResponse resp, Method method) { // 拿到当前类待执行的方法参数 Class[] clazzParams = method.getParameterTypes(); // 定义存储参数的数组 Object[] args = new Object[clazzParams.length]; int argsIndex = 0; // 判定此 class 对象所表示的类或接口与指定的 class 参数所表示的类或接口是否相同 // 或是否是其超类或超接口 for (int index = 0; index < clazzParams.length; index++) { if(ServletRequest.class.isAssignableFrom(clazzParams[index])){ args[argsIndex ++] = req; } if(ServletResponse.class.isAssignableFrom(clazzParams[index])){ args[argsIndex ++] = resp; } Annotation[] annotations = method.getParameterAnnotations()[index]; if(annotations.length > 0){ for (Annotation annotation : annotations) { if(RequestParam.class.isAssignableFrom(annotation.getClass())){ RequestParam requestParam = (RequestParam) annotation; // 找到注解的名字 args[argsIndex ++] = req.getParameter(requestParam.value()); } } } } return args; } /** * 依赖注入:对带有{@link org.feng.annotation.Autowired}的属性赋值 */ private void autowiredField() { beans.forEach((key, value) ->{ Class clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ // 获取属性 Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { if(!declaredField.isAnnotationPresent(Autowired.class)){ continue; } // 当存在 Autowired 标注的属性时 Autowired autowired = declaredField.getAnnotation(Autowired.class); String beanName; if("".equals(autowired.value())){ beanName = lowerFirstChar(declaredField.getType().getSimpleName()); } else { beanName = autowired.value(); } // 设置访问控制权限:原先是 private 不能访问 declaredField.setAccessible(true); // 自定义接口实现类:以Impl结尾,前边拼接接口名(首字母小写) if(beanName.endsWith("Impl")){ beanName = beanName.replace("Impl", ""); } if(beans.get(beanName) != null){ try { // 给声明的变量赋值:注入实例 declaredField.set(value, beans.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }); } /** * 创建实例:遍历所有的{@code class}文件,创建需要创建的实例存储到beans中
* 判断:是否被指定注解标注了 *
    *
  • 首先判断是不是{@link org.feng.annotation.Service}注解的类,若是则判断有没有传入的{@code service}名称
  • *
  • 其他情况,包括是{@link org.feng.annotation.Controller}的情况,全部使用小写类名为{@code key}
  • *
*/ private void createInstance() { try { // 遍历所有的.class文件;将需要实例化的类创建实例 for (String classPath : classPathList) { Class clazz = Class.forName(classPath.replace(".class", "")); if(clazz.isAnnotationPresent(Service.class)){ Service service = clazz.getAnnotation(Service.class); String key = service.value(); // 当传入了注解中参数时 if(!"".equals(key)){ beans.put(key, clazz.newInstance()); LOGGER.info("created instance by " + classPath); } else { // 获取第一个接口的简单名称,首字母小写 beans.put(lowerFirstChar(clazz.getInterfaces()[0].getSimpleName()), clazz.newInstance()); LOGGER.info("created instance by " + classPath); } } else if(clazz.isAnnotationPresent(Controller.class)){ // 以类名小写首字母为key beans.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance()); LOGGER.info("created instance by " + classPath); } } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { LOGGER.error("error in createInstance", e); } } /** * 将类名中的首字母小写 * @param simpleName 类名(不含包名) */ private String lowerFirstChar(String simpleName) { char[] chars = simpleName.toCharArray(); chars[0] += 32; return new String(chars); } /** * 先通过传入的包名拼接出文件路径: *

* {@code "org.feng".replace(".", "/");} *

* 递归扫描指定路径下的所有{
@code class}文件; * 存储{@code class}文件路径到集合中 * @param basePackage 扫描包的包名 */ private void scanPackage(String basePackage) { // 将包名转换为class文件路径 String resourceName = "/" + basePackage.replace(".", "/"); URL url = this.getClass().getClassLoader().getResource(resourceName); // 获取文件 assert url != null; String filename = url.getFile(); File file = new File(filename); // 获取所有文件 String[] files = file.list(); assert files != null; for (String path : files) { File fileTemp = new File(filename + path); // 当前如果是目录,递归扫描包 String packageName = basePackage + "." + path; if(fileTemp.isDirectory()){ scanPackage(packageName); } else { // 当扫描到文件(.class文件),增加到类路径集合 classPathList.add(packageName); LOGGER.info("scan " + packageName + " into classpath list"); } } } }

 

 

测试

org.feng.service.MyService

package org.feng.service;

/**
 * Created by Feng on 2019/12/17 9:23
 * CurrentProject's name is hand-springmvc
 * @author Feng
 */
public interface MyService {
    /**
     * 说
     * @return 字符串
     */
    String say();

 

org.feng.service.impl.MyServiceImpl

package org.feng.service.impl;

import org.feng.annotation.Service;
import org.feng.service.MyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by Feng on 2019/12/17 9:25
 * CurrentProject's name is hand-springmvc
 * @author Feng
 */
@Service
public class MyServiceImpl implements MyService {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyServiceImpl.class);

    public MyServiceImpl(){
        LOGGER.info("no args constructor MyServiceImpl.class");
    }

    @Override
    public String say() {
        return "MyServiceImpl invoking say()";
    }
}

 

org.feng.controller.MyController

package org.feng.controller;

import org.feng.annotation.Autowired;
import org.feng.annotation.Controller;
import org.feng.annotation.RequestMapping;
import org.feng.annotation.RequestParam;
import org.feng.service.MyService;

/**
 * Created by Feng on 2019/12/17 9:27
 * CurrentProject's name is hand-springmvc
 */
@RequestMapping("/MyController")
@Controller
public class MyController {

    @Autowired
    private MyService myService;

    @RequestMapping("/say.do")
    public String say(@RequestParam("name") String name, @RequestParam("info") String info){
        System.out.println("name = " + name + ", info = " + info);
        myService.say();
        return "redirect:/index.jsp";
    }
}

 

运行

配置tomcat

手写SpringMVC_第2张图片

运行结果:

手写SpringMVC_第3张图片

 

 

————————————————

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ

你可能感兴趣的:(手写SpringMVC)