自己手動開發一個簡易版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
謝謝