原始碼分析系列 | 從零開始寫MVC框架
1. 前言
前段時間在網上無意中上參與了一節騰訊課堂的公開課,裡面講到了一些分析思路,感覺挺有意思,也學習到了別人的一些講課技巧,正好自己也打算對過往知識網路做個整理回顧,計劃後面開展一系列原始碼分析教程,本章先從一個入門簡單的手寫MVC框架入門,模仿springMVC一些基本原理帶領大家通過自己實現MVC框架的過程瞭解到一些MVC框架的核心原理,從而在以後學習一些新的MVC框架或者使用springMVC過程中更從容。在本章學習過程中,大家可以暫時拋開springMVC的概念,通過實現過程去理解。
友情提示:
大家在一開始實現程式碼過程中,可以不必糾結太多設計模式、程式碼風格的問題,先按照基本思路實現功能,再優化
2. 為什麼要自己手寫框架
模仿優秀的開源框架可以加深我們對框架的理解,讓我們更加深刻地理解其原理,從而在日後使用框架過程中遇到問題也可以快速定位解決,甚至能開發更優秀的框架來滿足業務需求,而不僅僅只是停留在使用階段
以車主和4S店修理員對話舉個栗子:
以下是車主和4S店修理員的對話,車主是一個不瞭解汽車的小白,大家想象下他在修車過程中會發生什麼事?
場景:是汽車電池沒電,啟動不了

如上圖情景,假如車主自己對汽車結構和原理有一定了解的話,那他就可以對這次的維修費用項心裡有底,不會隨便被維修員忽悠,減少無謂的金錢損失。如果我們自己對框架有深入的瞭解,就可以對框架使用更得心應手,對使用的框架有更多的思考,而不僅僅是一個只會用工具的馬畜。
3. 簡單MVC框架設計思路
按照以往經驗,框架應用一般會分為3個階段:
- 配置階段
- 初始化階段
- 執行階段

4. 課程目標
成功根據使用者請求URL交給對應的contoller處理,並響應結果到瀏覽器

5. 編碼實戰
5.1 配置階段
這個階段是完成框架啟動或者執行時依賴的一些配置前期準備操作。
例如:
如果沒有配置web.xml,tomcat容器啟動的時候就不會攔截請求交給你MVC框架指定的servlet類上;
如果沒有指定一定的掃描規則(註解/xml),MVC框架就不知道該怎麼載入使用者自定義的類或者反射呼叫哪些元素;
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Eshare Web Application</display-name>
<!-- mvcframework config start -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>com.eshare.framework.mvc.servlet.EsDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:config.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- mvcframework config end -->
<!-- welcome page -->
<welcome-file-list>
<welcome-file>/index.html</welcome-file>
</welcome-file-list>
</web-app>
- 第12行:配置自定義分發器用於處理使用者請求分到到具體的處理類
- 第15行:配置配置檔案的路徑
- 第21行:配置請求攔截規則
config.properties
建立config.properties,指定框架載入的包名(配置檔案是由框架使用者自己建立,這裡為了演示方便提前建立好)
package-scan=使用者自定義包名
自定義註解
自定義註解用於標記哪些元素需要交給框架IOC管理或者需要框架去做一些列操作的,在本次Demo主要是定義以下幾個註解:
- EsController 標識用作控制器的類,放在類上方
- EsService 標識用作具體業務處理服務類,放在類上方
- EsAutowired 標識需要注入的屬性,放在屬性上方
- EsRequestMapping 請求規則對映,放在控制器的類或者方法上方
- EsRequestParam 標識方法中自定義引數,放在處理方法引數左方
EsController
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsController {
String value() default "";
}
- 第1行:@Target({ElementType.TYPE})指定放在目標類、介面、列舉宣告上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用範圍是執行時
EsService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsService {
String value() default "";
}
- 第1行:@Target({ElementType.TYPE})指定放在目標類、介面、列舉宣告上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用範圍是執行時
EsAutowired
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsAutowired {
String value() default "";
}
- 第1行:@Target({ElementType.FIELD})指定放在目標屬性欄位宣告上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用範圍是執行時
EsRequestMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsRequestMapping {
String value() default "";
}
- 第1行:@Target({ElementType.TYPE})指定放在目標類、介面、列舉宣告上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用範圍是執行時
EsRequestParam
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsRequestParam {
String value() default "";
}
- 第1行:@Target({ElementType.PARAMETER})指定放在目標引數宣告上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用範圍是執行時
5.2 初始化階段
建立自定義EsDispatcherServlet
EsDispatcherServlet是繼承HttpServlet
public class EsDispatcherServlet extends HttpServlet
重寫HttpServlet的init(ServletConfig config),定義好我們接下來要實現的幾個步驟,然後在針對每個方法一一填充邏輯,如下:
@Override
public void init(ServletConfig config) throws ServletException {
try {
//1.讀取配置
doLoadConfig(config);
String packageName = configProperties.getProperty("package-scan");
//2.掃描指定包下的類
doScanClass(packageName);
//3.對掃描出來的類例項化
doInitializeInstance();
//4.執行類依賴自動注入
doAutowired();
//5.配置url和handler對映關係handlerMapping
doHandlerMapping();
} catch (IOException e) {
e.printStackTrace();
}
}
- 第5行:doLoadConfig方法用於讀取使用者自定義配置
- 第6行:根據配置中的package-scan提取使用者指定掃描的包路徑
- 第8行:doScanClass方法用於掃描使用者指定包下的類,然後把要載入的類名存放下來
- 第10行:doInitializeInstance方法用於執行初始化例項,這個階段就是IOC初始化階段
- 第12行:doAutowired方法使用者對使用者加上該註解的屬性,由MVC框架反射注入
- 第14行:doHandlerMapping方法用於配置url和handler對映關係,用於後續MVC處理使用者請求對映到具體類的某個方法
doLoadConfig載入使用者自定義配置
這裡需要先定義一個全域性的properties例項,用於載入檔案後存放配置檔案資訊
/**
* 配置
*/
private Properties configProperties = new Properties();
根據web.xml上的配置資訊,去找到使用者自定義配置
/**
* 載入配置
*
* @param config
*/
private void doLoadConfig(ServletConfig config) throws IOException {
String configFilePath = config.getInitParameter("contextConfigLocation");
String configName = configFilePath.replace("classpath*:", "");
InputStream in = this.getClass().getClassLoader().getResourceAsStream(configName);
configProperties.load(in);
}
- 第7行:從web.xml配置中,根據contextConfigLocation去找到檔案具體路徑值
- 第8行:這裡是為了方便演示,直接把classpath相關的去掉,把classpath*:後的值作為載入路徑
- 第9行:利用類載入器去載入指定路徑下的檔案流
doScanClass掃描指定包下的類
根據上一步獲取的指定包路徑,去掃描對應的類,並把要載入的類名存放起來,這裡需要先定義一個集合存放類名
/**
* 需要載入的類列表
*/
private List<String> classNames = new ArrayList<String>();
接下來開始遍歷指定包目錄,遞迴掃描class檔案
private void doScanClass(String packageName) {
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
File file = new File(url.getFile());
for (File f : file.listFiles()) {
if (f.isDirectory()) {
doScanClass(packageName + "." + f.getName());
} else {
String className = packageName + "." + f.getName().replace(".class", "");
//存放到類名集合
classNames.add(className);
}
}
}
- 第1-2行:這裡是對包名轉換成位元組碼目錄相對路徑,例如com.eshare.demo轉換成/com/eshare/demo,然後根據url去載入檔案
- 第4-13行:遍歷目錄下的檔案,遇到目錄就以包名方式繼續遞迴尋找,如果是檔案就對檔名稱轉換成類全限定名,放到集合裡
doInitializeInstance類例項化
這裡對應的是spring裡面IOC容器初始化階段,對類例項化並存放到容器中,這裡我們定義一個集合模擬IOC容器
/**
* 類全限定名與例項對映集合,模擬applicationContext容器
*/
private Map<String, Object> applicationContext = new ConcurrentHashMap<String, Object>();
接下來我們完成類例項化工作,原理很簡單,其實就是利用反射建立類例項
private void doInitializeInstance() {
//如果不存在要載入的類,則直接返回
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
//檢視當前類位元組碼是否存在EsController、EsService註解
if (clazz.isAnnotationPresent(EsController.class)) {
String beanName = lowerInitial(className);
applicationContext.put(beanName, clazz.newInstance());
} else if (clazz.isAnnotationPresent(EsService.class)) {
EsService esService = clazz.getAnnotation(EsService.class);
//檢查是否存在自定義名稱
String beanName = esService.value().trim();
if (!"".equals(beanName.trim())) {
applicationContext.put(beanName, clazz.newInstance());
continue;
}
//沒有自定義名稱,則以介面名稱作為key
Class<?>[] interfaces = clazz.getInterfaces();
for (Class i : interfaces) {
applicationContext.put(i.getName(), clazz.newInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 第3-6行:先判斷是否存需要例項化的類,不存在則直接返回
- 第11-13行:這裡是對作為控制器的類進行例項化,這裡預設對類名首字母轉為小寫作為KEY,存放在IOC容器中
- 第14-28行:這裡是是針對業務服務類進行例項化,@EsCotroller和@EsService標註的類例項化有點不同,前者是控制器,後者是業務服務類,一般來說@EsService的類還會充當其他類的屬性使用,因此這裡的IOC容器中的key名會跟後面依賴注入的名稱有一定聯絡,這裡優先是使用使用者自定義名稱作為key,否則以介面名稱與例項作為對映存放
上面還依賴到一個工具類方法lowerInitial,這裡也順便寫下
private String lowerInitial(String className) {
char[] chars = className.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
- 第3行:這裡使用acsii碼方式進行轉換,提升轉換效能
doAutowired依賴注入
這一步原理也比較簡單,就是利用反射呼叫目標類的setObject方法進行注入,主要私有成員變數要進行暴力破解,否則訪問不了
private void doAutowired() {
if (applicationContext.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : applicationContext.entrySet()) {
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
//暴力破解許可權
field.setAccessible(true);
//檢查是否帶有@Autowired註解
if (field.isAnnotationPresent(EsAutowired.class)) {
EsAutowired esAutowired = field.getAnnotation(EsAutowired.class);
String beanName = esAutowired.value().trim();
if ("".equals(beanName)) {
beanName = field.getType().getName();
}
try {
field.set(entry.getValue(), applicationContext.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
continue;
}
}
}
}
}
- 第7行:獲取所有宣告的成員變數
- 第10行:對私有變數進行暴力破解,這樣後面才可以對私有變數進行復制
- 第12-23行:對帶有@Autowired註解的成員變數才進行注入,優先使用使用者自定義的名稱,否則使用成員變數名稱作為key從IOC容器找到對應的類實力進行賦值
doHandlerMapping配置URL和具體Handler方法對映規則
這一步最為複雜,我們首先要想到在執行階段MVC框架要根據使用者請求URL怎樣可以找到具體的對應方法,這時候我們應該是需要有一個URL匹配規則和method的對映關係,這樣才可以所有URL和method對應起來,但是隻有這兩個已知條件並不夠,用過反射呼叫方法都瞭解,反射呼叫具體方法時,需要的條件有引數和引數順序、目標類、目標方法,我們需要有一個東西可以讓URL和這些我們需要的條件裝起來,提供給執行階段使用,這時候我們可以建立一個Handler類去把我們需要反射用到的已知條件裝起來。
簡單整理下思路過程:
接下來我們就先建立這個Handler類,用於存放URL匹配規則和其他已知條件
class Handler {
private Pattern pattern;
private Object controller;
private Method method;
private Map<String, Integer> paramMapping;
public Handler(Pattern pattern, Object controller, Method method, Map<String, Integer> paramMapping) {
this.pattern = pattern;
this.controller = controller;
this.method = method;
this.paramMapping = paramMapping;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Map<String, Integer> getParamMapping() {
return paramMapping;
}
public void setParamMapping(Map<String, Integer> paramMapping) {
this.paramMapping = paramMapping;
}
}
因此定義的控制器可能會有多個,這裡需要用集合把handler存放起來
/**
* 處理器對映列表
*/
private List<Handler> handlerMapping = new ArrayList<Handler>();
接下來我們開始配置URL匹配規則和需要的已知條件的對映
/**
* 執行處理類對映
*/
private void doHandlerMapping() {
if (applicationContext.isEmpty()) return;
for (Map.Entry entry : applicationContext.entrySet()) {
//先獲取controller上的requestMapping值
Class<?> clazz = entry.getValue().getClass();
String baseUrl = "";
//檢查是否為controller
if (clazz.isAnnotationPresent(EsController.class)) {
if (clazz.isAnnotationPresent(EsRequestMapping.class)) {
EsRequestMapping esRequestMapping = clazz.getAnnotation(EsRequestMapping.class);
baseUrl = esRequestMapping.value();
}
}
//獲取當前類的所有方法
Method[] method = clazz.getMethods();
for (Method m : method) {
//方法上是否存在EsRequestMapping註解
if (!m.isAnnotationPresent(EsRequestMapping.class)) {
continue;
}
EsRequestMapping esRequestMapping = m.getAnnotation(EsRequestMapping.class);
String customRegex = ("/" + baseUrl + esRequestMapping.value()).replaceAll("/+", "/");
String reqRegex = customRegex.replaceAll("\\*", ".*");
Map<String, Integer> paramMapping = new HashMap<String, Integer>();
//獲取方法中的引數,對有自定義註解的引數將其名稱和順序對映放到集合中
Annotation[][] paramAnnotations = m.getParameterAnnotations();
for (int i = 0; i < paramAnnotations.length; i++) {
for (Annotation a : paramAnnotations[i]) {
//如果註解是EsRequestParam型別
if (a instanceof EsRequestParam) {
String paramName = ((EsRequestParam) a).value();
if (!"".equals(paramName)) {
paramMapping.put(paramName, i);
}
}
}
}
//處理非自定義註解引數,request,response
Class<?>[] types = m.getParameterTypes();
for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
String paramName = type.getName();
paramMapping.put(paramName, i);
}
}
//建立handler
handlerMapping.add(new Handler(Pattern.compile(reqRegex), entry.getValue(), m, paramMapping));
System.out.println("Mapping " + reqRegex + " " + m);
}
}
}
- 第5行:IOC容器如果沒有任何元素,則直接返回,不作處理
- 第6行:開始對IOC容器裡面的元素進行遍歷
- 第8-16行:先獲取controller上@EsRequestMapping註解的自定義的URL請求路徑,作為baseURL
- 第18-30行:獲取當前類的所有方法,然後根據方法上的@EsRequestMapping註解定義的URL請求路徑與baseURL進行拼接成一個完整的方法請求路徑,然後對URL路徑轉換成所有請求URL的通用正則表示式,/xxx/xxx.*
- 第35-47:遍歷方法中的所有帶有自定義註解的引數,獲取@EsRequestParam註解中定義的引數名,引數名和對應的引數順序存放到paramMapping
- 第48-56:遍歷所有非自定義註解的引數,如reqeust,respon原生自帶的引數取其類名作為key與引數順序對映存放到paramMapping
以上所有MVC框架初始化階段的工作已經完成了
5.3 執行階段
執行階段主要就是通過使用者URL請求路徑匹配到對應的目標控制器中的方法,利用反射將引數傳到方法進行呼叫,這裡我們先重寫doPost和doGet方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
boolean isSuccess = processRequest(req, resp);
if (!isSuccess) {
resp.getWriter().write("404 Page Not Found !");
}
}
- 第8-10行:把請求傳到我們自定義的執行方法中,如果執行失敗則模擬一個異常輸出到瀏覽器
5.3.1 processRequest處理使用者請求
/**
* 處理請求對映
*
* @param req
* @param resp
* @return
*/
private boolean processRequest(HttpServletRequest req, HttpServletResponse resp) {
if (handlerMapping.isEmpty()) {
return false;
}
try {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
//去掉Url中的上下文,保留請求資源路徑
url = url.replace(contextPath, "").replaceAll("/+", "/");
//遍歷handlerMapping,查詢匹配的handler去處理
for (Handler handler : handlerMapping) {
Matcher matcher = handler.getPattern().matcher(url);
if (!matcher.matches()) {
continue;
}
Method targetMethod = handler.getMethod();
//獲取方法引數型別
Class<?>[] paramTypes = targetMethod.getParameterTypes();
Object[] targetParamsValues = new Object[paramTypes.length];
//對於使用者自定義引數,從請求引數獲取使用者傳參
Map<String, String[]> paramMap = req.getParameterMap();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
//轉換陣列格式為字串,去掉"[]",注意存在多個引數時要把單詞變為“,”分割
String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
//假如引數集合不包含當前請求引數,直接跳過
if (!handler.getParamMapping().containsKey(entry.getKey())) {
continue;
}
//取出當前傳入引數在方法裡的索引位置
Integer index = handler.getParamMapping().get(entry.getKey());
//進行型別轉換並複製
targetParamsValues[index] = castStringToTargetType(value, paramTypes[index]);
}
//對於非使用者自定義引數,如容器自帶request和response,獲取它們在集合中的索引,直接注入
Integer reqIndex = handler.getParamMapping().get(HttpServletRequest.class.getName());
targetParamsValues[reqIndex] = req;
Integer respIndex = handler.getParamMapping().get(HttpServletResponse.class.getName());
targetParamsValues[respIndex] = resp;
resp.getWriter().write((String) targetMethod.invoke(handler.getController(), targetParamsValues));
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
- 第10-11行:如果使用者沒有定義任何作為控制器的類,則直接返回
- 第14-17行:獲取請求的url,這裡注意的是使用者請求URL會帶有專案上下文路徑,而我們handler匹配的url規則裡面是沒有上下文路徑的,因此要把它去掉,如:使用者定義的它專案名是test,那麼他請求路徑就會變成localhost:8080/test/hello/sayHello.do,這裡會截斷/test這部分,只保留/hello/sayHello.do
- 第20-23行:利用現有的handler中的pattern去匹配請求URL是否符合規則,不符合則直接獲取下一個handler
- 第24-30行:獲取匹配到對應的method,聲明後面呼叫方法需要用到的一些已知條件
- 第33-51行:遍歷請求的引數集合,根據使用者的請求引數型別從之前存放好的引數名稱與引數順序集合中匹配,獲取對應的引數順序,然後一個一個將請求引數按順序填充到targetParamsValues陣列中,用作反射呼叫方法的傳參,這裡執行完直接呼叫resp.getWriter().write輸出結果到瀏覽器。第35行要注意的是,因為一個傳參可能是一個可變陣列會有多個引數[arg0][arg1][arg2],所以這裡把資料轉換成字串再進行格式轉換為arg0,arg1,arg2。第43行castStringToTargetType會對傳參與目標方法的引數型別進行轉換,因為這裡獲取的請求引數都是字串型別,需要轉為目標方法的引數型別才能正常呼叫。
以上已經完成MVC框架的開發了,接下來我們建立一個demo去測試下框架是否正常執行
6. 框架測試
配置config
配置掃描包名是com.eshare.demo
package-scan=com.eshare.demo
建立控制器Controller類
/**
* hello控制器
* Created by liangyh on 2018/6/20.
*/
@EsController
@EsRequestMapping("/hello")
public class HelloController {
@EsAutowired
private HelloService helloService;
@EsRequestMapping("/sayHello.do")
public String sayHello(HttpServletRequest request, HttpServletResponse response,
@EsRequestParam("message") String message){
return helloService.sayHello(message);
}
}
建立業務處理Service類
/**
* Hello業務處理類介面
* Created by liangyh on 2018/6/23.
*/
public interface HelloService {
public String sayHello(String message);
}
建立業務處理Service介面實現類
/**
* Created by liangyh on 2018/6/23.
*/
@EsService
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String message) {
return "hello :"+message;
}
}
瀏覽器輸入URL測試
相關推薦
原始碼分析系列 | 從零開始寫MVC框架
1. 前言 前段時間在網上無意中上參與了一節騰訊課堂的公開課,裡面講到了一些分析思路,感覺挺有意思,也學習到了別人的一些講課技巧,正好自己也打算對過往知識網路做個整理回顧,計劃後面開展一系列原始碼分析教程,本章先從一個入門簡單的手寫MVC框架入門
[筆記]架構探險-從零開始寫JavaWeb框架-1. 之搭建輕量級mvc框架
囉嗦一句: 看md語法寫的文章,注意檢視 上面 的目錄. 一般是很有節奏的導航. ヽ(ˋ▽ˊ)ノヽ(ˋ▽ˊ)ノ 終於到了不會的地步了,該書的前面兩章節都是從零開始講解怎麼使用 idea搭建專案,從servlet開始講解怎麼使用. (idea的使用目錄)
[筆記]架構探險-從零開始寫JavaWeb框架-2.1. 之使框架具有aop特性-aop框架載入與切面執行流程分析
囉嗦一句:本筆記只是自己在學習過程中的一些分析和理解,看的人不一定能看懂.如果有興趣還是去買這本書看.筆記就當是另外一種解說好了 在本章節中會學習到如下的技術: 如何理解並使用代理技術 如何使用Spring提供的AOP技術(忽略,太多知識) 如何使
從零開始寫JavaWeb框架(第二章節)
oca ext span logs http ioe 請求方法 servlet 類型 這一章太多了。。。好累,不想寫那麽細了,就做一點總結吧。 package org.smart4j.chapter2.controller; import java.io.IOExcep
從零開始寫javaweb框架筆記9-細節完善與程式碼優化-完善控制器層
在這一小節中,我們開始寫jsp頁面 開啟/WEB-INF/view/customer.jsp檔案,完成如下程式碼: <%-- Created by IntelliJ IDEA. User: jack Date: 2015/12/5 Time:
架構探險-從零開始寫Javaweb框架讀書筆記(5)
AOP實現 AOP(Aspect Oriented Programming,面向切面程式設計);用來不改變程式碼的情況下在方法前後加入效能監控,日誌列印等等。 依照慣例,有時spring aop的實現過程 advice 直譯為通知 黃勇老師
從零開始寫JavaScript框架(一)
1. 模組的定義和載入 1.1 模組的定義 一個框架想要能支撐較大的應用,首先要考慮怎麼做模組化。有了核心和模組載入系統,外圍的模組就可以一個一個增加。不同的JavaScript框架,實現模組化方式各有不同,我們來選擇一種比較優雅的方式作個講解。 先
從零開始寫一個框架的詳細步驟
定位 所謂定位就是回答幾個問題,我出於什麼目的要寫一個框架,我的這個框架是幹什麼的,有什麼特性適用於什麼場景,我的這個框架的使用者物件是誰,他們會怎麼使用,框架由誰維護將來怎麼發展等等。 如果你打算寫框架,那麼肯定心裡已經有一個初步的定位,比如它是一個快取框架、Web
[閑的蛋疼系列]從零開始用TypeScript寫React的UI組件(0)-先寫一個Button??
component extension lap exp closed struct app target 參數 0.鹹魚要說的 一入前端深似海,鹹魚入海更加鹹。 最近閑的蛋疼,手上年前的事也完成了7788了,借助[PG1]的話來說,我們要keep real. 鹹魚肯定不
從零開始寫C# MVC框架之--- 配置log4日誌
寫入 出錯 fill 文件 幫助 fontsize att 日誌處理 引用 在框架中配置日誌分2步,一個是在幫助項目Zy.Utilities--Zy.Utility.Core中新建log類,封裝寫入日誌方法,還需要在Zy.Utility.Core添加 log4net 的引用
從零開始寫自己的PHP框架系列教程(二)[App.php]
porting col config exce tro efault fig 默認 clas 從這一個文件開始以後加載的均以類加載,請註意命名空間和所在文件的路徑 APP.php的這個類所在路徑:根目錄\framework\App.php 直接上代碼 namespace
從零開始寫C# MVC框架之--- 專案結構
框架總分2個專案:Web開發專案、幫助類專案 (ZyCommon、Zy.Utilities) 1、ZyCommon,是Web開發專案結構。新建一個空解決方案,再建Data、Service、ZyWeb解決方案資料夾,把資料層、介面服務層、Web層區分開
從零開始寫C# MVC框架之--- 用autofac ioc 容器實現依賴注入
本章查找了一篇對依賴注入解釋得非常不錯的文章為基礎,再加上自己的理解,不然還真不好用語言的方式表達清楚,引用下面這位仁兄的文章 依賴注入產生的背景: 隨著面向物件分析與設計的發展,一個良好的設計,核心原則之一就是將變化隔離,使得變化部分發生變化時,不變部
從零開始寫C# MVC框架之--- 資料庫表設計
怎麼在專案中使用Code First? 1、設計好資料庫表實體類,欄位--繫結對應屬性(是否非空,長度限制等),繫結到上下文基類中 2、使用Migrations遷移報告,把表生成到資料庫中 拿專案中的使用者表做演示: 詳細操作如下, 1.1、在Zy.Xn.Model專
[Golang] 從零開始寫Socket Server(3): 對長、短連接的處理策略(模擬心跳)
microsoft ted 每次 range 點擊 關閉 ade 而在 href 通過前兩章,我們成功是寫出了一套湊合能用的Server和Client,並在二者之間實現了通過協議交流。這麽一來,一個簡易的socket通訊框架已經初具雛形了,那麽我們接下來做的
從零開始搭建android框架系列(轉)
bsp andro hup 開始 blank class and lan com 網址:從零開始搭建android框架系列 githup:https://github.com/CameloeAnthony/Ant從零開始搭建android框架系列(轉)
從零開始寫STL-容器-雙端隊列
這一 偏移 nis log index end ref 分配 locate 從零開始寫STL-容器-雙端隊列 什麽是雙端隊列?在介紹vector源碼,我們發現在vector前端插入元素往往會引起大量元素的重新分配,雙端隊列(deque)就是為了解決這一問題,雙端隊列中在首
從零開始寫STL—functional
binder 保存 函數調用 mark 獲取 AR ref 返回 log function C++11 將任意類型的可調用(Callable)對象與函數調用的特征封裝到一起。 這裏的類是對函數策略的封裝,將函數的性質抽象成組件,便於和algorithm庫配合使用 基本運
一起學習造輪子(三):從零開始寫一個React-Redux
導致 href dispatch 判斷 som render connect mis 回調 本文是一起學習造輪子系列的第三篇,本篇我們將從零開始寫一個React-Redux,本系列文章將會選取一些前端比較經典的輪子進行源碼分析,並且從零開始逐步實現,本系列將會學習Prom
從零開始寫bootloader
inf boot bsp 開始 src 技術 text tex gif 從零開始寫bootloader