之前的一篇 手写 Spring,这样的文章很多,到处都是,要说真的能简单手写出 Spring MVC 其实不多,因为要理解,记忆,实践才能掌握,这不是一篇博客就能实现的,可能需要两篇。。再说面试的时候,要不要说呢,自己还没底。。自定义命名部分为加 X- 前缀,请自行理解
1、创建项目,准备 Jar 包
2、properties 和 web.xml
3、自定义注解(XController,XRequestMapping,XService,XAutowired)
4、XDispatchServlet(重点)
(1)继承 HttpServlet,重写 doGet,doPost 方法
(2)在 doPost 中先用注释,写出思路,7 步
(3)逐个填写方法
这里代码有的为图片,建议手敲,需要的话可查看:手写 Spring
(2)需要一个 Jar 包
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
dependency>
(1)web.xml 配置
和 一配一对
初始化的时候加载 properties 文件
中用 /*
(2)application.properties 配置要扫描的包名
scan-package=com.xiaopengwei
先只定义 4 个注解(XController,XRequestMapping,XService,XAutowired),定义的时候想想使用的地方,用于区别注解的 Target。
@Target 说明该 Annotation 可以修饰的对象范围
@Retention 描述注解的生命周期
@Documented 用于描述其它类型的 annotation 被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XAutowired {
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XController {
String value() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XRequestMapping {
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XService {
String value() default "";
}
整理思路:
初始化,启动阶段
1、加载配置文件
2、扫描相关的类
3、初始化 IOC 容器
4、依赖注入
5、初始化 HandlerMapping
6、运行时进行匹配
运行,匹配阶段
1、处理请求,进行匹配
(1)继承 HttpServlet,重写 doGet,doPost,init 方法
提示:IDEA 快捷键,Alt + Insert 和 Ctrl + O 都可以重写方法
重写方法,将 deGet 也走 doPost
(2)在 init 方法中先用注释,写出思路,搭建骨架
1、加载配置文件
2、扫描相关的类
3、初始化 IOC 容器
4、依赖注入
5、初始化 HandlerMapping
6、运行时进行匹配
(3)加载配置文件 doLoadConfig()
在调用时添加参数,注意名称和 web.xml 中的参数名称一致
/**
* 属性配置文件
*/
private Properties contextConfig = new Properties();
/**
* servletConfig 参数是 web.xml 中配置的字符串为 application.properties
* @param servletConfig servletConfig
*/
private void doLoadConfig(String servletConfig) {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(servletConfig);
try {
contextConfig.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
// InputStream 不会自动关闭
if (resourceAsStream != null) {
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(4) 扫描相关的类 doScanner()
在调用时添加参数,注意名称和 web.xml 中的参数名称一致
private List<String> classNameList = new ArrayList<>();
/**
* 扫描相关的类
* @param scanPackage properties --> scan-package
*/
private void doScanner(String scanPackage) {
// . 转化为 /
URL resourcePath = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
// 递归结束条件
if (resourcePath == null) { return; }
// 或 filePath 对应的 File 对象
File classPath = new File(resourcePath.getFile());
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
// 子目录递归
doScanner(scanPackage + "." + file.getName());
} else {
if (!file.getName().endsWith(".class")) { continue; }
// 转换成 com.xiaopengwei.TestController 的形式
String className = (scanPackage + "." + file.getName()).replace(".class", "");
// 保存在内容
classNameList.add(className);
}
}
}
(5)初始化 IOC 容器 doInstance()
/**
* IOC 容器
*/
Map<String, Object> iocMap = new HashMap<String, Object>();
/**
* 初始化 IOC 容器,将所有相关的类实例保存到 IOC 容器中
*/
private void doInstance() {
if (classNameList.isEmpty()) { return; }
try {
for (String className : classNameList) {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(XController.class)) {
String beanName = toLowerFirstCase(clazz.getSimpleName());
Object instance = clazz.newInstance();
// 保存在 ioc 容器
iocMap.put(beanName, instance);
} else if (clazz.isAnnotationPresent(XService.class)) {
String beanName = toLowerFirstCase(clazz.getSimpleName());
// 如果注解包含自定义名称, 例如 @Service("testService")
XService xService = clazz.getAnnotation(XService.class);
if (!"".equals(xService.value())) {
beanName = xService.value();
}
Object instance = clazz.newInstance();
iocMap.put(beanName, instance);
// getInterfaces 此方法返回这个类中实现接口的数组
for (Class<?> i : clazz.getInterfaces()) {
if (iocMap.containsKey(i.getName())) {
throw new Exception("The Bean Name Is Exist.");
}
// 接口不能实例化,接口存实现类的实例
iocMap.put(i.getName(), instance);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取类的首字母小写的名称
*
* @param className ClassName
* @return java.lang.String
*/
private String toLowerFirstCase(String className) {
char[] charArray = className.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
(6)依赖注入 doAutowired()
/**
* 依赖注入
*/
private void doAutowired() {
if (iocMap.isEmpty()) { return; }
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
// 获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的生命字段
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(XAutowired.class)) {
continue;
}
// 获取注解对应的类
XAutowired xAutowired = field.getAnnotation(XAutowired.class);
String beanName = xAutowired.value().trim();
// 获取 XAutowired 注解的值
if ("".equals(beanName)) {
beanName = field.getType().getName();
}
// 只要加了注解,都要加载,不管是 private 还是 protect
field.setAccessible(true);
try {
field.set(entry.getValue(), iocMap.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
(7)初始化 HandleMapping
Map<String, Method> handlerMapping = new HashMap<String, Method>();
/**
* 5、初始化 HandlerMapping
*/
private void initHandlerMapping() {
if (iocMap.isEmpty()) { return; }
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(XController.class)) { continue; }
String baseUrl = "";
if (clazz.isAnnotationPresent(XRequestMapping.class)) {
XRequestMapping xRequestMapping = clazz.getAnnotation(XRequestMapping.class);
baseUrl = xRequestMapping.value();
}
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(XRequestMapping.class)) { continue; }
XRequestMapping xRequestMapping = method.getAnnotation(XRequestMapping.class);
String url = ("/" + baseUrl + "/" + xRequestMapping.value()).replaceAll("/+", "/");
handlerMapping.put(url, method);
}
}
}
(8)进行匹配 doDispatch()
Map<String, Method> handlerMapping = new HashMap<String, Method>();
/**
* 5、初始化 HandlerMapping
*/
private void initHandlerMapping() {
if (iocMap.isEmpty()) { return; }
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(XController.class)) { continue; }
String baseUrl = "";
if (clazz.isAnnotationPresent(XRequestMapping.class)) {
XRequestMapping xRequestMapping = clazz.getAnnotation(XRequestMapping.class);
baseUrl = xRequestMapping.value();
}
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(XRequestMapping.class)) { continue; }
XRequestMapping xRequestMapping = method.getAnnotation(XRequestMapping.class);
String url = ("/" + baseUrl + "/" + xRequestMapping.value()).replaceAll("/+", "/");
handlerMapping.put(url, method);
}
}
}
提示:手写 Spring 中代码更全
TestController
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
*
* 前置控制器
*
* @author XiaoPengwei
* @since 2019-07-19
*/
@XController
@XRequestMapping("/test")
public class TestController {
@XAutowired
ITestXService testXService;
/**
* 测试方法 /test/query
*
* @param req 请求体
* @param resp 响应体
*/
@XRequestMapping("/query")
public void query(HttpServletRequest req, HttpServletResponse resp) {
if (req.getParameter("username") == null) {
try {
resp.getWriter().write("param username is null");
} catch (IOException e) {
e.printStackTrace();
}
} else {
String paramName = req.getParameter("username");
try {
resp.getWriter().write("param username is " + paramName);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("[INFO-req] New request param username-->" + paramName);
}
}
/**
* 测试方法 /test/listClassName
*
* @param req 请求体
* @param resp 响应体
*/
@XRequestMapping("/listClassName")
public void listClassName(HttpServletRequest req, HttpServletResponse resp) {
String str = testXService.listClassName();
System.out.println("testXService----------=-=-=>" + str);
try {
resp.getWriter().write(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
ITestService
public interface ITestXService {
String listClassName();
}
TestServiceImpl
@XService
public class TestXServiceImpl implements ITestXService {
@Override
public String listClassName() {
// 假装来自数据库
return "123456TestXServiceImpl";
}
}