12.12 手寫spring框架,幫你徹底瞭解spring的工作原理機制

在我們的日常工作中,經常會用到Spring、Spring Boot、Spring Cloud、Struts、Mybatis、Hibernate等開源框架,有了這些框架的誕生,平時的開發工作量也是變得越來越輕鬆,我們用 Spring Boot 分分鐘可以新建一個Web項目。 今天通過手寫Spring框架,幫大家深入瞭解一下Spring的工作機制,文中涉及的代碼只用來幫助大家理解Spring,不會在線上使用,有不嚴謹的地方還請大家掠過。

項目結構


手寫spring框架,幫你徹底瞭解spring的工作原理機制


框架部分實現

  1. 為了區分框架部分代碼和業務部分代碼,我們將這兩部分分別劃分在不同的包內 com.mars.demo 和 com.mars.framework,以便隨後只掃描業務代碼。
  2. 這裡是自己手寫Spring框架,所以不會引入任何Spring項目相關的包。
  3. 由於是一個Web項目,所有我們需要引入 servlet-api 包,僅供編譯器使用,所有配置 scope 為 provided。

新建一個Servlet

首先新建一個 HttpServlet 的實現類 MarsDispatcherServlet,用來接收請求。


public class MarsDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//6. 處理請求
}

@Override
public void init(ServletConfig config) throws ServletException {

}

配置web.xml

br> "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Spring Mvc Education/<display-name>

<servlet>
<servlet-name>marsmvc/<servlet-name>
<servlet-class>com.mars.framework.servlet.MarsDispatcherServlet/<servlet-class>
<init-param>
<param-name>contextConfigLocation/<param-name>
<param-value>application.properties/<param-value>
/<init-param>

<load-on-startup>1/<load-on-startup>
/<servlet>

<servlet-mapping>
<servlet-name>marsmvc/<servlet-name>
<url-pattern>/*/<url-pattern>
/<servlet-mapping>
/<web-app>
  1. 首先配置了一個 servlet, 名字是 marsmvc, 類全路徑是 com.mars.framework.servlet.MarsDispatcherServlet。
  2. 設置了初始化參數名和值(這裡的值是整個項目的配置文件)。
  3. 配置 load-on-startup, 標記容器是否在啟動的時候就加載這個servlet(實例化並調用其init()方法)。
  4. 配置 servlet-mapping, 將所有請求轉發到這個servlet處理。

配置application.properties

scanPackage=com.mars.demo

這個比較好理解,僅配置了一項內容,意思是要掃描的包,隨後我們會獲取這個值去加載容器。

定義我們常用的註解

  1. MarsAutowired
  2. MarsController
  3. MarsRequestMapping
  4. MarsRequestParam
  5. MarsService 這裡僅列舉兩個,其他都大同小異,需要源碼的可以去我的代碼倉庫fork。@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MarsController { String value() default ""; } @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MarsRequestMapping { String value() default ""; }

充實Servlet功能

先列出框架在初始化的時候都要做那些事情

  1. 加載配置文件
  2. 掃描所有相關聯的類
  3. 初始化所有相關聯的類,並且將其保存在IOC容器裡面
  4. 執行依賴注入(把加了@Autowired註解的字段賦值)
  5. 構造HandlerMapping,將URL和Method進行關聯

接下來我們一步步完成上面的操作

 @Override
public void init(ServletConfig config) throws ServletException {
System.out.println("===================");
//1.加載配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));

//2.掃描所有相關聯的類
doScanner(contextConfig.getProperty("scanPackage"));

//3.初始化所有相關聯的類,並且將其保存在IOC容器裡面
doInstance();

//4.執行依賴注入(把加了@Autowired註解的字段賦值)
doAutowired();

//Spring 和核心功能已經完成 IOC、DI

//5.構造HandlerMapping,將URL和Method進行關聯
initHandlerMapping();

System.out.println("Mars MVC framework initialized");

}

加載配置文件

 private Properties contextConfig = new Properties();

private void doLoadConfig(String location) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);

try {
contextConfig.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {

e.printStackTrace();
}
}
}
}

掃描所有相關聯的類


private void doInstance() { 

if(classNames.isEmpty()) return;

for(String className: classNames) {

try {
Class> clazz = Class.forName(className);


if(clazz.isAnnotationPresent(MarsController.class)) {

Object instance = clazz.newInstance();
String beanName = lowerFirstCase(clazz.getSimpleName());
ioc.put(beanName, instance);

} else if (clazz.isAnnotationPresent(MarsService.class)) {

MarsService service = clazz.getAnnotation(MarsService.class);

//2.優先使用自定義命名
String beanName = service.value();

if("".equals(beanName.trim())) {
//1.默認使用類名首字母小寫
beanName = lowerFirstCase(clazz.getSimpleName());
}

Object instance = clazz.newInstance();

ioc.put(beanName, instance);

//3.自動類型匹配(例如:將實現類賦值給接口)

Class> [] interfaces = clazz.getInterfaces();

for(Class> inter: interfaces) {
ioc.put(inter.getName(), instance);
}

}

} catch (Exception e) {
e.printStackTrace();
}
}

}

//利用ASCII碼的差值
private String lowerFirstCase(String str) {
char[] chars = str.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}

執行依賴注入(把加了@Autowired註解的字段賦值)

private void doAutowired() {

if(ioc.isEmpty()) return;

for(Map.Entry<string> entry: ioc.entrySet()) {
//注入的意思就是把所有的IOC容器中加了@Autowired註解的字段賦值
//包含私有字段
Field[] fields = entry.getValue().getClass().getDeclaredFields();

for(Field field : fields) {

//判斷是否加了@Autowired註解
if(!field.isAnnotationPresent(MarsAutowired.class)) continue;

MarsAutowired autowired = field.getAnnotation(MarsAutowired.class);

String beanName = autowired.value();

if("".equals(beanName)) {

beanName = field.getType().getName();
}

//如果這個字段是私有字段的話,那麼要強制訪問
field.setAccessible(true);
try {
field.set(entry.getValue(), ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/<string>

構造HandlerMapping,將URL和Method進行關聯

private void initHandlerMapping() {
if(ioc.isEmpty()) return;

for(Map.Entry<string> entry : ioc.entrySet()) {
Class> clazz = entry.getValue().getClass();

if(!clazz.isAnnotationPresent(MarsController.class)) continue;

String baseUrl = "";

if(clazz.isAnnotationPresent(MarsRequestMapping.class)) {
MarsRequestMapping requestMapping = clazz.getAnnotation(MarsRequestMapping.class);
baseUrl = requestMapping.value();
}

Method[] methods = clazz.getMethods();

for(Method method : methods) {

if(!method.isAnnotationPresent(MarsRequestMapping.class)) continue;

MarsRequestMapping requestMapping = method.getAnnotation(MarsRequestMapping.class);

String regex = requestMapping.value();

regex = (baseUrl + regex).replaceAll("/+", "/");

Pattern pattern = Pattern.compile(regex);
handlerMapping.add(new Handler(entry.getValue(), method, pattern));

System.out.println("Mapping: " + regex + "," + method.getName());
}
}

}
/<string>

編寫業務代碼

新建一個Controller

@MarsController
@MarsRequestMapping("/demo")
public class DemoApi {

@MarsAutowired
private DemoService demoService;

@MarsRequestMapping("/query")
public void query(HttpServletRequest req,
HttpServletResponse resp,
@MarsRequestParam("name") String name) {
System.out.println("name: " + name);
String result = demoService.get(name);

try{
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}

@MarsRequestMapping("/add")
public void add(HttpServletRequest req,
HttpServletResponse resp,
@MarsRequestParam("a") Integer a,
@MarsRequestParam("b") Integer b) {
try {
resp.getWriter().write(String.format("%d+%d=%d", a, b, (a+b)));
} catch (IOException e) {
e.printStackTrace();
}
}
}

提供兩個接口,一個通過請求名稱返回響應的介紹內容,另一個將請求的兩個Integer相加並返回。

創建一個Service

public interface DemoService {
String get(String name);
}

@MarsService
public class DemoServiceImpl implements DemoService {
public String get(String name) {
return String.format("My name is %s.", name);
}
}

添加Jetty插件

我們的項目運行在Jetty中,所以添加相關插件以及配置:

<plugin>
<groupid>org.mortbay.jetty/<groupid>
<artifactid>jetty-maven-plugin/<artifactid>
<version>7.1.6.v20100715/<version>
<configuration>
<stopport>9988/<stopport>
<stopkey>foo/<stopkey>
<scanintervalseconds>5/<scanintervalseconds>
<connectors>
<connector>
<port>8080/<port>
<maxidletime>60000/<maxidletime>
/<connector>
/<connectors>
<webappconfig>
<contextpath>//<contextpath>
/<webappconfig>
/<configuration>
/<plugin>

運行


手寫spring框架,幫你徹底瞭解spring的工作原理機制


點擊 jetty:run 運行項目

瀏覽器訪問: http://localhost:8080/demo/query?name=Mars


手寫spring框架,幫你徹底瞭解spring的工作原理機制


瀏覽器訪問:http://localhost:8080/demo/add?a=10&b=20


手寫spring框架,幫你徹底瞭解spring的工作原理機制


這樣一個完整的spring框架就已經手寫出來了,大家也可以關注下我的宮眾浩【java開發之路】 為大家準備好了2019最新的面試教程和架構師資料,感謝大家的支持與關注!


分享到:


相關文章: