自己手动开发一个简易版SpringMVC框架
1 【骨架】项目框架搭建
使用Gradle进行依赖管理
- 首先在Idea中选择File—>New—>Project,在左侧选择Gradle—>Java,填写一些项目信息,这里名为mini-spring,创建项目完成
- 之后,在项目上右键New—>Module,在左侧选择Gradle—>Java,ArtifactId填为framework,创建模块完成
- 重复上面的步骤,创建一个名为test的module,作为测试mini-spring功能的模块
- 由于不需要在主模块下编写代码,删掉主目录下的src目录
至此,项目搭建完成,目录结构如下:
要编写自己的spring框架需要先了解真正的spring的主要结构,如图:
因此我们的mini-spring结构如下:
- 实现core模块,包括Beans、Core、Context包;
- 实现web模块,集成Web和WebMVC
- 添加starter,实现类springboot的启动方式
在framework模块中创建如下的package:
在test模块中添加启动类Application并添加main方法输出hello world语句。
在test模块中的build.gradle文件添加如下代码:
jar {
manifest {
attributes "Main-Class": "cn.edu.neusoft.Application"
}
from {
configurations.compile.collect() {
it.isDirectory() ? it : zipTree(it)
}
}
}
在terminal中使用gradle build
,java -jar
命令查看是否成功输出hello world
为了把framework模块打包到test模块中,需要在dependencies
中添加compile(project(':framework'))
,这样test模块打出的jar包就会包含framework模块的代码了
接着,在framework的starter包中建立一个类MiniApplication,添加如下方法:
public static void run(Class<?> clz, String[] args) {
System.out.println("hello mini-spring");
}
这样做是模拟了springboot的启动方法,在test的Application类的main方法中调用MiniApplication.run(Application.class, args);
重新执行gradle build
,java -jar
命令查看是否成功输出hello world和hello mini-spring
2 【载体】集成服务器之Web服务器&Servlet
由于服务器进程的编写涉及到Java的Socket编程、HTTP请求的封装和服务器IO等知识,较为复杂,这里我们的框架仿照springboot的方式内置一个服务器(Tomcat)。
集成Tomcat
① 首先要为容器引入一个Tomcat包,这里选择目前比较流行的8.5.23版本。在framework模块的build.gradle文件的dependencies中引入compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
②在web包下新建一个server子包并创建TomcatServer类,添加如下代码:
public class TomcatServer {
//tomcat配置
private Tomcat tomcat;
private String[] args;
public TomcatServer(String[] args) {
this.args = args;
}
public void startServer() throws LifecycleException {
tomcat = new Tomcat();
tomcat.setPort(6699);//设置监听端口
tomcat.start();
//防止服务器中途退出
Thread awaitThread = new Thread("tomcat_await_thread") {
@Override
public void run() {
//声明tomcat一直在等待
TomcatServer.this.tomcat.getServer().await();
}
};
//设置线程为非守护线程
awaitThread.setDaemon(false);
awaitThread.start();
}
}
③在starter包下的MiniApplication类中添加
TomcatServer tomcatServer = new TomcatServer(args);
try {
tomcatServer.startServer();
} catch (LifecycleException e) {
e.printStackTrace();
}
④gradle build clean
后再执行java -jar test-1.0-SNAPSHOT.jar
看到控制台输出tomcat信息代表程序编写成功(此时浏览器访问http://localhost:6699会404,是因为我们还没有编写servlet处理请求)
⑤修改TomcatServer类的代码
public class TomcatServer {
//tomcat配置
private Tomcat tomcat;
private String[] args;
public TomcatServer(String[] args) {
this.args = args;
}
public void startServer() throws LifecycleException {
tomcat = new Tomcat();
tomcat.setPort(6699);//设置监听端口
tomcat.start();
//建立tomcat与servlet的联系
Context context = new StandardContext();
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener());
TestServlet servlet = new TestServlet();
Tomcat.addServlet(context, "TestServlet", servlet).setAsyncSupported(true);
context.addServletMappingDecoded("/test.json", "TestServlet");
tomcat.getHost().addChild(context);
//防止服务器中途退出
Thread awaitThread = new Thread("tomcat_await_thread") {
@Override
public void run() {
//声明tomcat一直在等待
TomcatServer.this.tomcat.getServer().await();
}
};
//设置线程为非守护线程
awaitThread.setDaemon(false);
awaitThread.start();
}
}
重新打包运行项目并访问http://localhost:6699/test.json,看到输出test servlet!
,服务器正常响应
3 【入口】控制器Controller的实现
①修改TestServlet类名为DispatcherServlet(这里注意记得修改代码中其它引用了TestServlet的地方),修改context.addServletMappingDecoded("/test.json", "TestServlet");
为context.addServletMappingDecoded("/", "DispatcherServlet");
②在web包中添加mvc包,并添加@Controller、@RequestMapping、@RequestParam三个常用的注解,代码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
String value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestParam {
String value();
}
③在test模块中创建controllers包,并创建一个SalaryController类用来测试我们实现的注解
@Controller
public class SalaryController {
@RequestMapping("getSalary.do")
public Integer getSalary(@RequestParam("name") String name, @RequestParam("experience") String experience) {
return 1000;
}
}
④在core包中添加一个ClassScanner类添加如下代码:
public class ClassScanner {
public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException {
List<Class<?>> classList = new ArrayList<>();
String path = packageName.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
//处理资源类型为jar包的类型
if (resource.getProtocol().contains("jar")) {
JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
String jarFilePath = jarURLConnection.getJarFile().getName();
classList.addAll(getClassesFromJar(jarFilePath, path));
} else {
//todo
}
}
return classList;
}
private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
JarFile jarFile = new JarFile(jarFilePath);
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String entryName = jarEntry.getName();
if (entryName.startsWith(path) && entryName.endsWith(".class")) {
String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
classes.add(Class.forName(classFullName));
}
}
return classes;
}
}
⑤在框架入口类MiniApplication中修改run()方法为:
public static void run(Class<?> clz, String[] args) {
System.out.println("hello mini-spring");
TomcatServer tomcatServer = new TomcatServer(args);
try {
tomcatServer.startServer();
List<Class<?>> classList = ClassScanner.scanClasses(clz.getPackage().getName());
classList.forEach(it -> System.out.println(it.getName()));
} catch (Exception e) {
e.printStackTrace();
}
}
⑥运行结果
⑦在web包中添加handler包,添加MappingHandler类,代码如下:
public class MappingHandler {
private String uri;
private Method method;
private Class<?> controller;
private String[] args;
MappingHandler(String uri, Method method, Class<?> clz, String[] args) {
this.uri = uri;
this.method = method;
this.controller = clz;
this.args = args;
}
public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
String requestUri = ((HttpServletRequest) req).getRequestURI();
if (!uri.equals(requestUri)) {
return false;
}
Object[] parameters = new Object[args.length];
for (int i = 0; i < args.length; i++) {
parameters[i] = req.getParameter(args[i]);
}
Object ctl = controller.newInstance();
Object response = method.invoke(ctl, parameters);
res.getWriter().println(response.toString());
return true;
}
}
⑧在handler包中添加HandlerManager类,代码如下:
public class HandlerManager {
public static List<MappingHandler> mappingHandlerList = new ArrayList<>();
public static void resolveMappingHandler(List<Class<?>> classList) {
for (Class<?> cls : classList) {
if (cls.isAnnotationPresent(Controller.class)) {
parseHandlerFromController(cls);
}
}
}
private static void parseHandlerFromController(Class<?> cls) {
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(RequestMapping.class)) {
continue;
}
String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
List<String> paramNameList = new ArrayList<>();
for (Parameter parameter : method.getParameters()) {
if (parameter.isAnnotationPresent(RequestParam.class)) {
paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
}
}
String[] params = paramNameList.toArray(new String[paramNameList.size()]);
MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);
HandlerManager.mappingHandlerList.add(mappingHandler);
}
}
}
⑨在DispatcherServlet中的service方法中使用这些handler:
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
for (MappingHandler mappingHandler : HandlerManager.mappingHandlerList) {
try {
if (mappingHandler.handle(req, res)) {
return;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
⑩别忘了在MiniApplication中调用HandlerManager初始化所有的MappingHandler,添加HandlerManager.resolveMappingHandler(classList);
最后打包运行并在浏览器中访问localhost:6699/getSalary.do
,输出1000,成功。
4 【基石】Bean管理(IOC&DI)
①仿照sring添加两个bean相关的注解,在beans包下编写如下代码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AutoWired {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
}
②创建一个bean工厂,编写如下代码:
public class BeanFactory {
private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
public static Object getBean(Class<?> cls) {
return classToBean.get(cls);
}
public static void initBean(List<Class<?>> classList) throws Exception {
List<Class<?>> toCreate = new ArrayList<>();
while (toCreate.size() != 0) {
int remainSize = toCreate.size();
for (int i = 0; i < toCreate.size(); i++) {
if (finishCreate(toCreate.get(i))) {
toCreate.remove(i);
}
}
if (toCreate.size() == remainSize) {
throw new Exception("cycle dependency");
}
}
}
private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
if (!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)) {
return true;
}
Object bean = cls.newInstance();
for (Field field : cls.getDeclaredFields()) {
if (field.isAnnotationPresent(AutoWired.class)) {
Class<?> filedType = field.getType();
Object reliantBean = BeanFactory.getBean(filedType);
if (reliantBean == null) {
return false;
}
field.setAccessible(true);
field.set(bean, reliantBean);
}
}
classToBean.put(cls, bean);
return true;
}
}
③在启动类中使用bean工厂初始化类,修改代码如下:
try {
tomcatServer.startServer();
List<Class<?>> classList = ClassScanner.scanClasses(clz.getPackage().getName());
BeanFactory.initBean(classList);
HandlerManager.resolveMappingHandler(classList);
classList.forEach(it -> System.out.println(it.getName()));
}
④修改MappingHandler中的Object ctl = controller.newInstance();
为Object ctl = BeanFactory.getBean(controller);
⑤在test模块中添加service包,并添加一个测试类:
@Bean
public class SalaryService {
public Integer calSalary(Integer experience) {
return experience * 500;
}
}
⑥修改SalaryController的代码:
@Controller
public class SalaryController {
@AutoWired
private SalaryService salaryService;
@RequestMapping("getSalary.do")
public Integer getSalary(@RequestParam("name") String name, @RequestParam("experience") String experience) {
System.out.println("执行了controller方法");
return salaryService.calSalary(Integer.parseInt(experience));
}
}
⑦编译运行项目在浏览器访问http://localhost:6699/getSalary.do?experience=3&name=aa
返回对应的计算结果,成功
至此,我们的简易版SpringMVC的编写就完成了
我已经将完整代码放在Github上了
https://github.com/Fieeeeee/MySpringMVC.git
谢谢