环境描述

idea

java 8

1. POM文件

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.feng</groupId>
<artifactId>hand-springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging> <name>hand-springmvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>Example Domain</url> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties> <dependencies>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency> <!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency> <!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies> <build>
<finalName>hand-springmvc</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
</pluginManagement>
</build>
</project>

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

注解类

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<br>
* 核心控制器:
* <ul>实现思路
* <li>先扫描基础包,获取 {@code class} 文件的路径;其实是为了获取完整类名</li>
* <li>根据上边的完整类名以及判断是否有指定创建实例(通过有无注解和注解的类型)并保存实例到 {@code map} 中</li>
* <li>依赖注入变量,从实例获得类对象,然后解析 {@code Field} 并赋值给标注了{@link Autowired}的{@code Field}</li>
* <li>获取方法上的参数,通过{@link HttpServletRequest}获取</li>
* </ul>
* @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<String> classPathList = new ArrayList<>(); /**
* IOC容器:存放对象;使用{@link ConcurrentHashMap}保证线程安全
*/
private Map<String, Object> beans = new ConcurrentHashMap<>(16); /**
* 存放方法映射:使用{@link ConcurrentHashMap}保证线程安全<br>
* 用于存储方法<br>
* {@code key = classpath + methodPath; value = method}
*/
private Map<String, Method> handlerMap = new ConcurrentHashMap<>(16); /**
* 初始化数据:
* <ul>
* <li>扫描所有的类</li>
* <li>创建实例并存储进 {@code beans}</li>
* <li>依赖注入:使用 {@code Autowired}</li>
* <li>拼接请求地址初始化、方法映射</li>
* </ul>
*/
@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中<br>
* 判断:是否被指定注解标注了
* <ul>
* <li>首先判断是不是{@link org.feng.annotation.Service}注解的类,若是则判断有没有传入的{@code service}名称</li>
* <li>其他情况,包括是{@link org.feng.annotation.Controller}的情况,全部使用小写类名为{@code key}</li>
* </ul>
*/
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);
} /**
* 先通过传入的包名拼接出文件路径:
* <p>
* {@code "org.feng".replace(".", "/");}
* </p>
* 递归扫描指定路径下的所有{@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

运行结果:

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

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

手写SpringMVC的更多相关文章

  1. 《四 spring源码》手写springmvc

    手写SpringMVC思路 1.web.xml加载  为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...

  2. spring源码学习之springMVC(一)

    个人感觉<Spring技术内幕:深入解析Spring架构与设计原理(第2版)>这本书对spring的解读要优于<Spring源码深度解析(第2版)>这本书的,后者感觉就是再陈述 ...

  3. 《四 spring源码》手写springioc框架

    手写SpringIOCXML版本 /** * 手写Spring专题 XML方式注入bean * * * */ public class ClassPathXmlApplicationContext { ...

  4. 《四 spring源码》利用TransactionManager手写spring的aop

    事务控制分类 编程式事务控制          自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false);  // 设置手动控制事务 Hibern ...

  5. 史上最完整promise源码手写实现

    史上最完整的promise源码实现,哈哈,之所以用这个标题,是因为开始用的标题<手写promise源码>不被收录 promise自我介绍 promise : "君子一诺千金,承诺 ...

  6. 【spring源码学习】springMVC之映射,拦截器解析,请求数据注入解析,DispatcherServlet执行过程

    [一]springMVC之url和bean映射原理和源码解析 映射基本过程 (1)springMVC配置映射,需要在xml配置文件中配置<mvc:annotation-driven >  ...

  7. 《四 spring源码》spring的事务注解@Transactional 原理分析

    先了解什么是注解 注解 Jdk1.5新增新技术,注解.很多框架为了简化代码,都会提供有些注解.可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在代码上插入了一个插件. 注解不会也不能影 ...

  8. spring源码学习之springMVC(二)

    接着上一篇.继续来看springMVC中最和我们开发中接近的一部分内容: DispatcherServlet的逻辑处理 作者写到在DispatcherServlet类中存在doGet.doPost之类 ...

  9. Spring源码追踪4——SpringMVC View解析

    这次的议题是返回json和返回普通view经过的路线差异. ---------------------------------------------------------------------- ...

  10. Ubuntu搭建Spring源码环境常见问题

    在一心想要学习Spring框架源码时,我们会遇到很多麻烦的问题.开始本文前,你只需要拥有一个装好IDEA的Ubuntu系统就可以愉快启程了.如果还没有IDEA,可以参考在Ubuntu上安装Intell ...

随机推荐

  1. LESSON 1-Introduction

    Keywords: Communication system, Channel model, Channel capacity by Shannon 1. Two fundamental archit ...

  2. Git实战指南----跟着haibiscuit学Git(第一篇)

    笔名:  haibiscuit 博客园: https://www.cnblogs.com/haibiscuit/ Git地址: https://github.com/haibiscuit?tab=re ...

  3. java内存区域,jvm内存各个区域详解

    一.运行时数据区域 1.如图所示,可分为如下几个区域. 2.程序计数器 程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器.字节码解释器工作时就是通过改变这个计数器的 ...

  4. Chrome插件安装的3种方法,解决拖放不能安装的情况,并提供插件下载

    本文摘录于Chrome插件网站 方法一:拖放安装 下载插件的crx文件后,打开Chrome的扩展页面(chrome://extensions/或按Chrome菜单图标>更多工具>扩展程序) ...

  5. ThinkPHP多表查询之join方法

    现在的目的是要把article_category中的name字段导入到article中去 表yz_article如下 表yz_article_category如下

  6. ThinkPHP5——模型关联(一对一关联)

    定义 定义一对一关联使用了hasOne,hasOne方法的参数包括: hasOne('关联模型名','外键名','主键名',['模型别名定义'],'join类型'); 下面定义一个用户表,公司给每个用 ...

  7. 循环神经网络(RNN)的改进——长短期记忆LSTM

     一:vanilla RNN 使用机器学习技术处理输入为基于时间的序列或者可以转化为基于时间的序列的问题时,我们可以对每个时间步采用递归公式,如下,We can process a sequence ...

  8. Flink入门(二)——Flink架构介绍

    1.基本组件栈 了解Spark的朋友会发现Flink的架构和Spark是非常类似的,在整个软件架构体系中,同样遵循着分层的架构设计理念,在降低系统耦合度的同时,也为上层用户构建Flink应用提供了丰富 ...

  9. Python开发还在用virtualenv?不如了解下pipenv...#华为云·寻找黑马程序员#

    又见 Kenneth Reitz 之前公众号写了一篇文章爬虫新宠requests_html 带你甄别2019虚假大学,其中主要是为了介绍模块**requests_html,这个模块的作者还开发了req ...

  10. 带着canvas去流浪系列之四 绘制散点图

    [摘要] 用原生canvasAPI实现百度Echarts图表 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvasAPI ...