本文章隻是仿寫SpringMvc架構的第一篇,後續會增加Aop、HandlerInterceptor、Valid、掃描xml檔案等功能。
首先說下仿寫SpringMvc(mini)需要實作的幾個核心功能:實作Ioc容器(Beanfactory),掃描注解解析出需要處理的類(controller、bean、RequestMapping、AutoWired注解等),實作SpringMvc核心的DispatcherServlet以及Handler等。
本項目基于idea開發
1、建立項目,建立一個maven工程導入相關依賴,主要是tomcat的依賴和配置打成jar包後程式的入口
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.34</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.dy.minispringMvc.MiniSpringMvcApp</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
注意:mainClass是你自己的main方法所在類,這裡主要是為了配置jar包的程式入口。
打包時候點選左上角的Maven,然後點選install即可,打完後的包可以直接運作。
2、整合tomcat伺服器,這裡**注意 TomcatServer.this.tomcat.getServer().await();**開啟tomcat服務等待。不然運作的時候刷一下就過去了。
package com.dy.minispringMvc.core;
import com.dy.minispringMvc.Servlet.CustomDispatcherServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
public class TomcatServer {
private Tomcat tomcat;
private String[] args;
public TomcatServer(String[] args) {
this.args = args;
}
//把自定義servlet注冊到tomcat容器當中
public void startServer() throws LifecycleException {
tomcat = new Tomcat();
tomcat.setPort(7788);
tomcat.start();
//建立一個Context對象
Context context = new StandardContext();
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener());
//執行個體化我們自己的CustomDispatcherServlet
CustomDispatcherServlet servlet = new CustomDispatcherServlet();
//注冊我們自己的CustomDispatcherServlet到tomcat
tomcat.addServlet(context, "servlet", servlet).setAsyncSupported(true);
//設定映射,這裡是7788端口下所有路徑都會到我們自己的CustomDispatcherServlet
context.addServletMappingDecoded("/", "servlet");
tomcat.getHost().addChild(context);
TomcatServer.this.tomcat.getServer().await();
}
}
下面是自己實作的CustomDispatcherServlet類,實作Servlet的service方法即可
public class CustomDispatcherServlet implements Servlet {
public void init(ServletConfig servletConfig) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.getWriter().println("hello SpringMc");
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
直接運作,然後可以看到tomcat列印資訊,證明tomcat整合完成
浏覽器通路7788端口下任意路徑看到如上圖,證明第一步整合tomcat容器成功。
2、建立核心類CustomClassLoader,這個類的目的很簡單,就是拿到基于目前main方法所在類的所有Class。
/**
* 自定義類加載 目的就是拿到目前jar包下所有java.lang.Class
* <p>
* 類的加載3個階段
* <p>
* 通過一個類的全限定名來擷取定義此類的二進制位元組流
* 将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構
* 在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口
* <p>
*/
public class CustomClassLoader {
public static List<Class<?>> loadeClass(String packageName) throws IOException, ClassNotFoundException {
List<Class<?>> classList = new ArrayList<Class<?>>();
String path = packageName.replace(".", "/");
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = contextClassLoader.getResources(path);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();//file:/C:/Users/root/Desktop/study/mini_springMvc/target/classes/com/dy/minispringMvc
//隻對jar包進行處理
if (url.getProtocol().contains("jar")) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
String jarPath = jarURLConnection.getJarFile().getName();//拿到jar包絕對路徑 C:\Users\root\Desktop\study\mini_springMvc\target\mini_springMvc-1.0-SNAPSHOT-shaded.jar
classList.addAll(getClassFromJar(jarPath, path));
} else {
//其他暫時不做處理
}
}
return classList;
}
private static List<Class<?>> getClassFromJar(String jarPath, String path) throws IOException, ClassNotFoundException {
List<Class<?>> classList = new ArrayList<Class<?>>();
JarFile jarFile = new JarFile(jarPath);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();//com/dy/minispringMvc/core/CustomClassLoader.class 這個jar包下的資源路徑
//如果是class檔案,并且是我們建的class,非依賴
if (name.startsWith(path) && name.endsWith(".class")) {
//去掉.class字尾
String className = name.replace("/", ".").substring(0, name.length() - 6);//com.dy.minispringMvc.core.CustomClassLoader
classList.add(Class.forName(className));
}
}
return classList;
}
}
代碼很簡單就是通過類的全限定名稱,拿到目前jar包下所有java.lang.Class對象,隻要知道java.lang.Class是什麼就行,代碼上方注解已經有說明。
我們拿到所有java.lang.Class對象就可以為所欲為了。
3、建立架構基礎容器,建立CustomBeanfactory,這裡需要說一下控制反轉(IOC)和依賴注入(DI)的差別,控制反轉通俗來說就是對Bean的控制交給架構去管理,是一種思想。而依賴注入則是一種手段,讓Bean的建立去依賴容器,從容器當中拿Bean。好處就是隻需要new一次,如果需要就去容器當中拿即可。
首先來看幾個自定義注解:
注意:controller也是一個特殊的Bean
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CustomAutoWired {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomBean {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomController {
}
定義好了注解我們才能知道哪些類需要交給容器管理
CustomBeanfactory:
public class CustomBeanfactory {
private static Map<Class<?>, Object> beanMap = new ConcurrentHashMap<Class<?>, Object>();
public static Object getBean(Class<?> cla) {
return beanMap.get(cla);
}
public static void ininBean(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
List<Class<?>> classList = new ArrayList<Class<?>>(classes);
while (!classList.isEmpty() && classList.size() > 0) {
//記錄一下需要掃描的總數
int size = classList.size();
for (int i = 0; i < classList.size(); i++) {
if (crateBean(classList.get(i))) {
classList.remove(i);
}
}
//如果出現互相依賴的情況,則抛出一個異常
if (size == classList.size()) {
throw new RuntimeException("建立失敗,暫未解決互相依賴問題");
}
}
}
private static boolean crateBean(Class<?> cla) throws IllegalAccessException, InstantiationException {
//如果是不需要處理的類,則直接删除
if (!cla.isAnnotationPresent(CustomBean.class) && !cla.isAnnotationPresent(CustomController.class)) {
return true;
}
Object obj = cla.newInstance();
Field[] declaredFields = cla.getDeclaredFields();
//設定需要建立的對象的屬性
for (Field field : declaredFields) {
//如果是CustomAutoWired注解修飾的屬性,則從我們的BeanFactory取,取不到則傳回false,因為while循環,會在後面循環建立成功
//找目前的依賴,當依賴建立好以後,直接從beanMap取即可。
if (field.isAnnotationPresent(CustomAutoWired.class)) {
Class<?> fieldType = field.getType();
Object fieldObj = beanMap.get(fieldType);
if (fieldObj == null) {
return false;
}
//将取到的依賴set到需要建立的bean中
//大坑 這裡必須設定為true,不然不能進行set
field.setAccessible(true);
field.set(obj, fieldObj);
}
}
beanMap.put(cla, obj);
return true;
}
}
4、建立Mvc架構的核心Handle和HandlerManager管理類
同樣的先看幾個簡單注解
注意:這兩個注解和上面注解不同的是CustomRequestMapping用于方法上的注解,CustomResqusetParam注解是用于屬性上的注解。其中CustomResqusetParam的value屬性是參數的key值,目的是為了友善用req.getParameter(“key”)取到請求參數,CustomRequestMapping的value則是映射的UrL
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomRequestMapping {
String value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CustomResqusetParam {
String value();
}
繼續使用我們CustomClassLoader.loadeClass(packName)方法拿到的所有Class對象為所欲為。
HandlerManager類,這個類主要是掃描出所有CustomController注解的類,把他變成一個Handler
**
* 建立所有Url和handler的映射
*/
public class HandlerManager {
private static Map<String, HandlerMapping> handlerMappingMap = new ConcurrentHashMap<String, HandlerMapping>();
public static HandlerMapping getHandler(String url) {
return handlerMappingMap.get(url);
}
public static void resolveMappingHandler(List<Class<?>> classList) {
for (Class<?> cls : classList) {
if (cls.isAnnotationPresent(CustomController.class)) {
parseHandlerFromController(cls);
}
}
}
private static void parseHandlerFromController(Class<?> cla) {
Method[] methods = cla.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(CustomRequestMapping.class)) {
continue;
}
String url = method.getDeclaredAnnotation(CustomRequestMapping.class).value();
List<String> parmasKey = new ArrayList<>();
for (Parameter parameter : method.getParameters()) {
if (!parameter.isAnnotationPresent(CustomResqusetParam.class)) {
continue;
}
parmasKey.add(parameter.getDeclaredAnnotation(CustomResqusetParam.class).value());
}
parmasKey.toArray();
String[] args = parmasKey.toArray(new String[parmasKey.size()]);
HandlerMapping handlerMapping = new HandlerMapping(url, method, cla, args);
handlerMappingMap.put(url, handlerMapping);
}
}
}
HandlerMapping 類
public class HandlerMapping {
private String url;
private Method method;
private Class<?> controller;
//請求參數的key
private String[] paramKey;
private String[] paramValue;
public HandlerMapping(String url, Method method, Class<?> controller, String[] args) {
this.url = url;
this.method = method;
this.controller = controller;
this.paramKey = args;
}
public boolean handler(HttpServletRequest req, HttpServletResponse res) throws InvocationTargetException, IllegalAccessException, IOException {
paramValue=new String[paramKey.length];
for (int i = 0; i < paramKey.length; i++) {
paramValue[i] = req.getParameter(paramKey[i]);
}
Object bean = CustomBeanfactory.getBean(controller);
Object response = method.invoke(bean, paramValue);
res.getWriter().println(response.toString());
return true;
}
}
最後,改造一下我們的CustomDispatcherServlet類的service方法,讓這個類完成他的目的,分發
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest;
HandlerMapping handler = HandlerManager.getHandler(httpServletRequest.getRequestURI());
if (handler!=null){
HttpServletResponse httpServletResponse= (HttpServletResponse) servletResponse;
try {
handler.handler(httpServletRequest,httpServletResponse);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
下面是main方法代碼:
public class MiniSpringMvcApp {
public static void main(String[] args) throws LifecycleException, IOException {
System.out.println("打包");
System.out.println(MiniSpringMvcApp.class.getPackage().getName());
try {
List<Class<?>> classList = CustomClassLoader.loadeClass(MiniSpringMvcApp.class.getPackage().getName());
CustomBeanfactory.ininBean(classList);
HandlerManager.resolveMappingHandler(classList);
} catch (Exception e) {
e.printStackTrace();
}
TomcatServer tomCatServer=new TomcatServer(args);
tomCatServer.startServer();
}
}
注意:測試時一定要打成jar包後測試,注解使用方法和SpringMvc架構一樣,請自行測試
本文章隻是仿寫SpringMvc架構的第一篇,後續會增加Aop、HandlerInterceptor、Valid、掃描xml檔案等功能。
gitHup位址:https://github.com/dengyu123456/mini_springMvc.git