2019独角兽企业重金招聘Python工程师标准>>>

迷你版Spring MVC 实现

本文参考自 写出我的第一个框架:迷你版Spring MVC ,写这篇文章用于个人学习的记录。

项目建立

首先,建立一个空Maven项目,在java目录下,建立以下包结构。

.....|--com.it|-annotation|-dao|-service|-webDispatchServlet

接下来,引入servlet依赖

<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version></dependency>

在annotation增加如下注解:

@Autowired.java

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {String value();}

@Controller.java

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

@Repository.java

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

@RequestMapping.java

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

@Service.java

@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

转载于:https://my.oschina.net/u/3226414/blog/3007161

迷你版Spring MVC 实现相关推荐

  1. 写出我的第一个框架:迷你版Spring MVC

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:张丰哲 https://www.jianshu.com/p/ ...

  2. 【手写系列】写出我的第一个框架:迷你版Spring MVC

    你没有看错标题,今天,我将实现我人生中第一个框架,^_^ 前期准备 我这里要写的是一个迷你版的Spring MVC,我将在一个干净的web工程开始开发,不引入Spring,完全通过JDK来实现. 我们 ...

  3. 手写一个迷你版Spring MVC框架

    前期准备 我这里要写的是一个迷你版的Spring MVC,我将在一个干净的web工程开始开发,不引入Spring,完全通过JDK来实现. 我们先来看一眼工程: 工程代码结构 第一:在annotatio ...

  4. 【手写系列】写一个迷你版的Tomcat

    前言 Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打交道.这是一只神奇的猫,今天让我来抽象你,实现你! Tomcat Write MyTomcat Tomcat是非常流行的 ...

  5. MyBatis+Spring MVC开发指南(一)

    前言 MyBatis+Spring MVC这套组合,在实际互联网项目中非常流行,博主工作中也涉及过,打算由浅入深.系统的写出来!这个系列将会涵盖MyBatis开发详解.Spring MVC开发详解,以 ...

  6. 详述 Spring MVC 启动流程及相关源码分析

    文章目录 Web 应用部署初始化过程(Web Application Deployement) Spring MVC 启动过程 Listener 的初始化过程 Filter 的初始化 Servlet ...

  7. Eclipse下创建Spring MVC web程序--非maven版

    首先, 安装eclipse和tomcat, 这里我下载的是tomcat9.0版本64位免安装的:地址https://tomcat.apache.org/download-90.cgi 免安装的如何启动 ...

  8. Spring MVC 基于Method的映射规则(注解版)

    在Restful风格的web开发中,根据不同的请求方法使用相应的控制器处理逻辑成为核心需求,下面就看看如何在Spring MVC中识别不同的请求方法. 请求方法 在Http中,请求的方法有很多种,最常 ...

  9. Spring MVC 过时了吗?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者 | 陈龙@知乎 来源 | https://www.z ...

最新文章

  1. 速度超快!字节跳动开源序列推理引擎LightSeq
  2. 详解MariaDB数据库的存储过程
  3. QQ亿级日活跃业务后台核心技术揭秘
  4. photo如何制作长图(外送搞笑毒故事)
  5. PIC在线升级源码分析
  6. Visual C# 2008+SQL Server 2005 数据库与网络开发--13.1.3 简单记事本程序菜单设计
  7. 4.1.2电路交换、报文交换与分组交换
  8. 【转载】送到榨油厂的飞鸽传书
  9. JS实例:网页上图片延迟加载的JS代码
  10. 解决mac安装homebrew后报错-bash: brew: command not found
  11. 你知道undefined与null的区别吗?
  12. 在尝试重新安装一个服务时遇到这样的错误:指定服务已标记为删除
  13. css改火狐滚动条样式_自定义滚动条,可解决火狐滚动条默认样式修改不了问题...
  14. springmvc应用-自定义参数解析器
  15. rar压缩包解密在线,忘记rar压缩包密码怎么找回?
  16. 台式计算机cpu ram hdd,什么是RAM?
  17. html5切西瓜游戏,体育游戏切西瓜教案
  18. 联想集团ESG与社会价值论坛召开,首次发布《联想集团2022社会价值报告》
  19. 《概率论与数理统计》之样本空间和随机事件
  20. 这几道Python面试题,稳准狠,Python面试题No15

热门文章

  1. lda进行图片分类_基于SIFT+Kmeans+LDA的图片分类器的实现
  2. kafka调试工具kafkacat的使用
  3. ntp-redhat 同步时间配置
  4. hibernate 高并发下遇到的大坑
  5. STM8S——8位基本定时器(TIM4)
  6. iis10 HTTP 错误 500.19 - Internal Server Error
  7. HDU 3641 Treasure Hunting(阶乘素因子分解+二分)
  8. A站有一个页面需要PV统计 A站读写该数据 B站读该数据 需要数据同步
  9. TinyFrame开篇:基于CodeFirst的ORM
  10. [译]时间自动机:语义,算法和工具