环境描述

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. C#Windows Forms窗体、按钮-xdd

    1.更换窗体图标 方法:单击窗体,更改icon属性. 2.调整窗体打开时默认位置 方法:单击窗体,更改StartPotion属性. 3.修改窗体大小 方法:单击窗体,更改Size属性. 4.设置窗体的 ...

  2. 使用three.js创建大小不随着场景变化的文字

    使用three.js创建大小不随着场景变化的文字,需要以下两步: 1.将文字绘制到画布上. 2.创建着色器材质,把文字放到三维场景中. 优点: 1.跟用html实现文字相比,这些文字可以被模型遮挡,更 ...

  3. Linux网络相关、firewalld和netfilter、netfilter5表5链介绍、iptables语法 使用介绍

    第7周第3次课(5月9日) 课程内容: 10.11 Linux网络相关10.12 firewalld和netfilter10.13 netfilter5表5链介绍10.14 iptables语法 扩展 ...

  4. c语言l博客作业02

    问题 答案 这个作业属于哪个课程 C语言程序设计l 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/SE2019-2/homework/8687 我在这个 ...

  5. 快捷键 导入命名空间shift +alt

  6. ThreadLocal 源码解读

    一.引入 public class Thread implements Runnable { /* 前面略 */ /* ThreadLocal values pertaining to this th ...

  7. 基于SCN增量恢复DG同步

    问题描述:做scn恢复备库的测试,吭哧了几天,今天终于可以记录一下,遇到了很多坑,作为初学者可以更好地理解DG,主要先关闭备库,在主库做归档丢失备库无法同步,备库产生GAP,然后增量备份恢复备库,版本 ...

  8. 使用正则表达式实现(加减乘除)计算器(C#实现)

    起因:公司领导要求做一款基于行业规范的计算器, 然后需要用户输入一些数据,然后根据用户输入的数据满足某些条件后,再根据用户输入的条件二进行加减乘除运算.;-) 期间因为查找规范等形成数据表的某一列是带 ...

  9. 用JS实现HTML转PDF

    遇到这个需求,现把实现代码整理出来,方便大家参考 <!-- html转PDF --> <script src="https://cdnjs.cloudflare.com/a ...

  10. 第五章 初始jQuery

    jQuery与JavaScript: jQuery的用途: 访问和操作DOM元素: 控制页面样式: 对页面事件的处理: 方便地使用jQuery插件: 与Ajax技术的完美结合: jQuery的优势: ...