在我們的日常工作中宙拉,經(jīng)常會(huì)用到Spring宾尚、Spring Boot、Spring Cloud谢澈、Struts煌贴、Mybatis、Hibernate等開源框架锥忿,有了這些框架的誕生牛郑,平時(shí)的開發(fā)工作量也是變得越來(lái)越輕松,我們用 Spring Boot 分分鐘可以新建一個(gè)Web項(xiàng)目敬鬓。
今天通過(guò)手寫Spring框架淹朋,幫大家深入了解一下Spring的工作機(jī)制,文中涉及的代碼只用來(lái)幫助大家理解Spring钉答,不會(huì)在線上使用础芍,有不嚴(yán)謹(jǐn)?shù)牡胤竭€請(qǐng)大家掠過(guò)。
項(xiàng)目結(jié)構(gòu)
框架部分實(shí)現(xiàn)
- 為了區(qū)分框架部分代碼和業(yè)務(wù)部分代碼数尿,我們將這兩部分分別劃分在不同的包內(nèi) com.mars.demo 和 com.mars.framework仑性,以便隨后只掃描業(yè)務(wù)代碼。
- 這里是自己手寫Spring框架右蹦,所以不會(huì)引入任何Spring項(xiàng)目相關(guān)的包诊杆。
- 由于是一個(gè)Web項(xiàng)目,所有我們需要引入 servlet-api 包何陆,僅供編譯器使用晨汹,所有配置 scope 為 provided。
新建一個(gè)Servlet
首先新建一個(gè) HttpServlet 的實(shí)現(xiàn)類 MarsDispatcherServlet甲献,用來(lái)接收請(qǐng)求宰缤。
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. 處理請(qǐng)求
}
@Override
public void init(ServletConfig config) throws ServletException {
}
配置web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"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>
- 首先配置了一個(gè) servlet, 名字是 marsmvc, 類全路徑是 com.mars.framework.servlet.MarsDispatcherServlet慨灭。
- 設(shè)置了初始化參數(shù)名和值(這里的值是整個(gè)項(xiàng)目的配置文件)朦乏。
- 配置 load-on-startup, 標(biāo)記容器是否在啟動(dòng)的時(shí)候就加載這個(gè)servlet(實(shí)例化并調(diào)用其init()方法)。
- 配置 servlet-mapping, 將所有請(qǐng)求轉(zhuǎn)發(fā)到這個(gè)servlet處理氧骤。
配置application.properties
scanPackage=com.mars.demo
這個(gè)比較好理解呻疹,僅配置了一項(xiàng)內(nèi)容,意思是要掃描的包筹陵,隨后我們會(huì)獲取這個(gè)值去加載容器刽锤。
定義我們常用的注解
- MarsAutowired
- MarsController
- MarsRequestMapping
- MarsRequestParam
- MarsService
這里僅列舉兩個(gè),其他都大同小異朦佩,需要源碼的可以去我的代碼倉(cāng)庫(kù)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 "";
}
充實(shí)Servlet功能
先列出框架在初始化的時(shí)候都要做那些事情
- 加載配置文件
- 掃描所有相關(guān)聯(lián)的類
- 初始化所有相關(guān)聯(lián)的類,并且將其保存在IOC容器里面
- 執(zhí)行依賴注入(把加了@Autowired注解的字段賦值)
- 構(gòu)造HandlerMapping语稠,將URL和Method進(jìn)行關(guān)聯(lián)
接下來(lái)我們一步步完成上面的操作
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("===================");
//1.加載配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2.掃描所有相關(guān)聯(lián)的類
doScanner(contextConfig.getProperty("scanPackage"));
//3.初始化所有相關(guān)聯(lián)的類宋彼,并且將其保存在IOC容器里面
doInstance();
//4.執(zhí)行依賴注入(把加了@Autowired注解的字段賦值)
doAutowired();
//Spring 和核心功能已經(jīng)完成 IOC、DI
//5.構(gòu)造HandlerMapping仙畦,將URL和Method進(jìn)行關(guān)聯(lián)
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();
}
}
}
}
# 掃描所有相關(guān)聯(lián)的類
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.優(yōu)先使用自定義命名
String beanName = service.value();
if("".equals(beanName.trim())) {
//1.默認(rèn)使用類名首字母小寫
beanName = lowerFirstCase(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
//3.自動(dòng)類型匹配(例如:將實(shí)現(xiàn)類賦值給接口)
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);
}
執(zhí)行依賴注入(把加了@Autowired注解的字段賦值)
private void doAutowired() {
if(ioc.isEmpty()) return;
for(Map.Entry<String, Object> 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();
}
//如果這個(gè)字段是私有字段的話输涕,那么要強(qiáng)制訪問(wèn)
field.setAccessible(true);
try {
field.set(entry.getValue(), ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
構(gòu)造HandlerMapping,將URL和Method進(jìn)行關(guān)聯(lián)
private void initHandlerMapping() {
if(ioc.isEmpty()) return;
for(Map.Entry<String, Object> 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());
}
}
}
編寫業(yè)務(wù)代碼
新建一個(gè)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();
}
}
}
提供兩個(gè)接口慨畸,一個(gè)通過(guò)請(qǐng)求名稱返回響應(yīng)的介紹內(nèi)容莱坎,另一個(gè)將請(qǐng)求的兩個(gè)Integer相加并返回。
創(chuàng)建一個(gè)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插件
我們的項(xiàng)目運(yùn)行在Jetty中寸士,所以添加相關(guān)插件以及配置:
<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 implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>8080</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
<webAppConfig>
<contextPath>/</contextPath>
</webAppConfig>
</configuration>
</plugin>
運(yùn)行
點(diǎn)擊 jetty:run 運(yùn)行項(xiàng)目
瀏覽器訪問(wèn): http://localhost:8080/demo/query?name=Mars
瀏覽器訪問(wèn):http://localhost:8080/demo/add?a=10&b=20
這樣一個(gè)完整的spring框架就已經(jīng)手寫出來(lái)了檐什,大家也可以關(guān)注下我的宮眾浩【java開發(fā)之路】
為大家準(zhǔn)備好了2019最新的面試教程和架構(gòu)師資料,感謝大家的支持與關(guān)注碉京!