手写SpringMVC
环境描述
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>
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的更多相关文章
- 《四 spring源码》手写springmvc
手写SpringMVC思路 1.web.xml加载 为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...
- spring源码学习之springMVC(一)
个人感觉<Spring技术内幕:深入解析Spring架构与设计原理(第2版)>这本书对spring的解读要优于<Spring源码深度解析(第2版)>这本书的,后者感觉就是再陈述 ...
- 《四 spring源码》手写springioc框架
手写SpringIOCXML版本 /** * 手写Spring专题 XML方式注入bean * * * */ public class ClassPathXmlApplicationContext { ...
- 《四 spring源码》利用TransactionManager手写spring的aop
事务控制分类 编程式事务控制 自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false); // 设置手动控制事务 Hibern ...
- 史上最完整promise源码手写实现
史上最完整的promise源码实现,哈哈,之所以用这个标题,是因为开始用的标题<手写promise源码>不被收录 promise自我介绍 promise : "君子一诺千金,承诺 ...
- 【spring源码学习】springMVC之映射,拦截器解析,请求数据注入解析,DispatcherServlet执行过程
[一]springMVC之url和bean映射原理和源码解析 映射基本过程 (1)springMVC配置映射,需要在xml配置文件中配置<mvc:annotation-driven > ...
- 《四 spring源码》spring的事务注解@Transactional 原理分析
先了解什么是注解 注解 Jdk1.5新增新技术,注解.很多框架为了简化代码,都会提供有些注解.可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在代码上插入了一个插件. 注解不会也不能影 ...
- spring源码学习之springMVC(二)
接着上一篇.继续来看springMVC中最和我们开发中接近的一部分内容: DispatcherServlet的逻辑处理 作者写到在DispatcherServlet类中存在doGet.doPost之类 ...
- Spring源码追踪4——SpringMVC View解析
这次的议题是返回json和返回普通view经过的路线差异. ---------------------------------------------------------------------- ...
- Ubuntu搭建Spring源码环境常见问题
在一心想要学习Spring框架源码时,我们会遇到很多麻烦的问题.开始本文前,你只需要拥有一个装好IDEA的Ubuntu系统就可以愉快启程了.如果还没有IDEA,可以参考在Ubuntu上安装Intell ...
随机推荐
- JAVA网络通信底层调用LINUX探究
前言:该博客花了我一个下午得心血,全部手打,路过给个赞,拒绝抄袭!!!!!!!!!!!!!!!!!!!!!!!!! 简单的SOCKET通信程序 先从一段简单的JAVA程序性开始写起,这里我们才用半双工 ...
- sql server 增删改(查太多了)
表: 学生(*学号,姓名,性别,年龄,专业) create table student( sno ) primary key, sname ) not null, ssex ), sage small ...
- easyui textbox 赋值
$('#fireInfo').textbox('setValue', tempData.fireInfo); $('#fireStartTime').datetimebox('setValue', t ...
- 【JavaEE】之MyBatis的ParameterType的使用
在MyBatis的Mapper.xml文件中,参数的表示方法有两种:一种是使用 “#{XXX}” 的方式表示的,另一种是使用 “${XXX}” 的方式表示的.今天来介绍以下这两种方式的不同之处. 1. ...
- 01Shell入门02-echo和printf
输出方式 小知识 echo echo -e 可以控制字体颜色和背景颜色输出 示例 echo -e "\033[41;36m Hello world \033[0m" [root@h ...
- MySQL数据库开发的36条原则【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- “智慧海绵城市”(SSC)监测评价体系整体解决方案
一.方案简介 无论是内涝防治.黑臭水体治理,还是海绵城市规划设计及建设.评估,乃至未来智慧城市的建设,都需要有全面.致密.大量的城市水文监测数据和先进模拟仿真技术作基础支撑,唯有如此,决策才有据可依, ...
- 浏览器主页锁定之战——IE:我太难了
精彩回顾: 我是一个explorer的线程 我是一个杀毒软件线程 我是一个IE浏览器线程 比特宇宙-TCP/IP的诞生 产品vs程序员:你知道www是怎么来的吗? Hello, World! 我是一个 ...
- python解析ifconfig 输出成字典
有个需求需要将ifcofig输出解析出来,这里将写的整理出来.方便后续使用. eth0 Link encap:Ethernet HWaddr 00:50:53:b2:23:e6 inet addr:1 ...
- CodeForces-Round235D
链接:http://codeforces.com/contest/401/problem/D 题意:给出一个数字num和m,问通过重新排列num中的各位数字中有多少个数(mod m)=0,直接枚举 ...