相關文章:
Spring5 - 30個類手寫實戰 - 打卡第一天(V1版本)
Spring5 - 30個類手寫實戰 - 打卡第二天(IOC與DI)
Spring5 - 30個類手寫實戰 - 打卡第四天(AOP)
1.MVC九大組件
序號 | 組件名 | 解釋 |
---|---|---|
1 | MultipartResolver | 多文件上傳的組件 |
2 | LocalResolver | 本地語言環境 |
3 | ThemeResolver | 主題模塊處理器 |
4 | HandlerMapping | 保存Url映射關係 |
5 | HandlerAdapter | 動態參數適配器 |
6 | HandlerExceptionResolver | 異常攔截器 |
7 | RequestToViewNameTranslator | 視圖提取器,從reques中獲取viewName |
8 | ViewResolvers | 視圖轉換器,模板引擎 |
9 | FlashMapManager | 參數緩存器 |
核心組件執行流程
項目結構:
2.代碼
PageAction
import com.liulin.spring.framework.annotation.LRequestParam;
import com.liulin.spring.framework.webmvc.servlet.LModelAndView;
import java.util.HashMap;
import java.util.Map;
/**
* Create by DbL on 2020/5/2 0002
*/
@LController
@LRequestMapping("/")
public class PageAction {
@LAutowired
IQueryService queryService;
@LRequestMapping("/first.html")
public LModelAndView query(@LRequestParam("coder") String teacher){
String result = queryService.query(teacher);
Map<String,Object> model = new HashMap<String,Object>();
model.put("coder", teacher);
model.put("data", result);
model.put("token", "123456");
return new LModelAndView("first.html",model);
}
}
first.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>My SpringMVC模板引擎演示</title>
</head>
<center>
<h1>大家好,我是¥{coder}<br/>歡迎大家一起來探索Spring的世界</h1>
<h3>Hello,My name is ¥{coder}</h3>
<div>¥{data}</div>
Token值:¥{token}
</center>
</html>
LDispatcherServlet
package com.liulin.spring.framework.webmvc.servlet;
import com.liulin.spring.framework.annotation.*;
import com.liulin.spring.framework.context.LApplicationContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Create by DbL on 2020/4/29 0029
*/
public class LDispatcherServlet extends HttpServlet {
private LApplicationContext applicationContext;
// IOC容器,key默認是類名首字母小寫,value是對應的實例對象
private List<LHandlerMapping> handlerMappings = new ArrayList<LHandlerMapping>();
private Map<LHandlerMapping, LHandlerAdapter> handlerAdapters = new HashMap<LHandlerMapping, LHandlerAdapter>();
private List<LViewResolver> viewResolvers = new ArrayList<LViewResolver>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 6.委派,根據URL去找到一個對應的Method並通過response返回
try {
doDispatch(req, resp);
} catch (Exception e) {
try {
processDispatchResult(req, resp, new LModelAndView("500"));
} catch (Exception ex) {
ex.printStackTrace();
resp.getWriter().write("500 Exception , Detail : " + Arrays.toString(e.getStackTrace()));
}
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
// 完成了對HandlerMapping的封裝
// 完成了對方法返回值的封裝,ModelAndView
// 1.通過URL獲取一個HandlerMapping
LHandlerMapping handler = getHandler(req);
if (handler == null) {
processDispatchResult(req, resp, new LModelAndView("404"));
return;
}
// 2.根據一個HandlerMapping獲取一個HandlerAdapter
LHandlerAdapter ha = getHandlerAdapter(handler);
// 3.解析某一個方法的形參和返回值之後,統一封裝爲ModelAndView對象
LModelAndView mv = ha.handler(req, resp, handler);
// 就把ModelAndView變成一個ViewResolver
processDispatchResult(req, resp, mv);
}
private LHandlerAdapter getHandlerAdapter(LHandlerMapping handler) {
if (this.handlerAdapters.isEmpty()){
return null;
}
return this.handlerAdapters.get(handler);
}
private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, LModelAndView lModelAndView) throws Exception {
if(lModelAndView == null){
return;
}
if(this.viewResolvers.isEmpty()){
return;
}
for(LViewResolver viewResolver : this.viewResolvers){
LView view = viewResolver.resolverViewName(lModelAndView.getViewName());
// 直接往瀏覽器輸出
view.render(lModelAndView.getModel(),req,resp);
return;
}
}
private LHandlerMapping getHandler(HttpServletRequest req) {
if (this.handlerMappings.isEmpty()) {
return null;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
for (LHandlerMapping mapping : handlerMappings) {
Matcher matcher = mapping.getPattern().matcher(url);
if (!matcher.matches()) {
continue;
}
return mapping;
}
return null;
}
@Override
public void init(ServletConfig config) throws ServletException {
// 初始化Spring的核心IO容器
applicationContext = new LApplicationContext(config.getInitParameter("contextConfigLocation"));
// 初始化九大組件
initStrategies(applicationContext);
//================MVC部分==============//
System.out.println("L Spring framework init success ");
}
private void initStrategies(LApplicationContext context) {
// 多文件上傳的組件
//initMultipartResolver(context);
// 初始化本地語言環境
// initLocalResolver(context);
// 初始化模板處理器
// initThemResolver(context);
// handlerMapping
initHandlerMapping(context);
// 初始化參數適配器
initHandlerAdapters(context);
// 初始化異常攔截器
// initHandlerExceptionResolvers(context);
// 初始化視圖預處理器
//initRequestToViewNameTranslator(context);
// 初始化視圖轉換器
initViewResolvers(context);
// Flash管理器
// initFlashMapManager(context);
}
private void initViewResolvers(LApplicationContext context) {
String templateRoot = context.getConfig().getProperty("templateRoot");
String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
File templateRootDir = new File(templateRootPath);
for(File file : templateRootDir.listFiles()){
this.viewResolvers.add(new LViewResolver(templateRoot));
}
}
private void initHandlerAdapters(LApplicationContext context) {
for (LHandlerMapping handlerMapping : handlerMappings){
this.handlerAdapters.put(handlerMapping,new LHandlerAdapter());
}
}
private void initHandlerMapping(LApplicationContext context) {
if (context.getBeanDefinitionCount() == 0) {
return;
}
for (String beanName : context.getBeanDefinitionNames()) {
Object instance = context.getBean(beanName);
Class<?> clazz = instance.getClass();
// 類沒有加註解的跳過
if (!clazz.isAnnotationPresent(LController.class)) {
continue;
}
// 如果類上定義了路徑,方法上的路徑需要拼接上此路徑
String baseUrl = "";
if (clazz.isAnnotationPresent(LRequestMapping.class)) {
LRequestMapping Mapping = clazz.getAnnotation(LRequestMapping.class);
baseUrl = Mapping.value();
}
// 只獲取public的方法
for (Method method : clazz.getMethods()) {
// 方法沒有加註解的跳過
if (!method.isAnnotationPresent(LRequestMapping.class)) {
continue;
}
LRequestMapping requestMapping = method.getAnnotation(LRequestMapping.class);
// 對於配置了“/” 和沒有配置“/”的通過正則表達式統一處理
// 將路徑中的*改爲正則表達式".*"的方式
String regex = ("/" + baseUrl + "/" + requestMapping.value().replaceAll("\\*", ".*")).replaceAll("/+", "/");
Pattern pattern = Pattern.compile(regex);
handlerMappings.add(new LHandlerMapping(pattern, instance, method));
System.out.println("mapped : " + regex + " , " + method);
}
}
}
}
LHandlerAdapter
package com.liulin.spring.framework.webmvc.servlet;
import com.liulin.spring.framework.annotation.LRequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Create by DbL on 2020/5/2 0002
*/
public class LHandlerAdapter {
public LModelAndView handler(HttpServletRequest req, HttpServletResponse resp, LHandlerMapping handler) throws Exception {
// 保存形參列表 將參數名稱和參數的位置保存起來
Map<String,Integer> paramIndexMapping = new HashMap<String,Integer>();
// 通過運行時的狀態去拿到註解的值
Annotation[][] pa = handler.getMethod().getParameterAnnotations();
for (int i = 0; i < pa.length; i++) {
for (Annotation a : pa[i]) {
if (a instanceof LRequestParam) {
String paramName = ((LRequestParam) a).value();
if (!"".equals(paramName.trim())) {
paramIndexMapping.put(paramName,i);
}
}
}
}
// 初始化
Class<?>[] paramTypes = handler.getMethod().getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
Class<?> paramterType = paramTypes[i];
if (paramterType == HttpServletRequest.class || paramterType == HttpServletResponse.class) {
paramIndexMapping.put(paramterType.getName(),i);
}else if (paramterType == String.class) {
}
}
// 拼接實參列表
// http://localhost:8080/web/add.json?name=DBL&addr=chongqing
Map<String, String[]> params = req.getParameterMap();
Object[] paramValues = new Object[paramTypes.length];
for(Map.Entry<String,String[]> param : params.entrySet()){
String value = Arrays.toString(params.get(param.getKey()))
.replaceAll("\\[|\\]","")
.replaceAll("\\s+","");
if(!paramIndexMapping.containsKey(param.getKey())){
continue;
}
int index = paramIndexMapping.get(param.getKey());
// 允許自定義的類型轉換器Converter
paramValues[index] = castStringValue(value,paramTypes[index]);
}
// String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
// method.invoke(applicationContext.getBean(beanName), paramValues);
if(paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
int index = paramIndexMapping.get(HttpServletRequest.class.getName());
paramValues[index] = req;
}
if(paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
int index = paramIndexMapping.get(HttpServletResponse.class.getName());
paramValues[index] = resp;
}
Object result = handler.getMethod().invoke(handler.getController(),paramValues);
if (result == null || result instanceof Void){
return null;
}
boolean isModelAndView = handler.getMethod().getReturnType() == LModelAndView.class;
if(isModelAndView){
return (LModelAndView) result;
}
return null;
}
private Object castStringValue(String value, Class<?> paramType) {
if(String.class == paramType){
return value;
}else if(Integer.class == paramType){
return Integer.valueOf(value);
}else if(Double.class == paramType){
Double.valueOf(value);
}else{
if(value != null ){
return value;
}
}
return null;
}
}
LHandlerMapping
package com.liulin.spring.framework.webmvc.servlet;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
/**
* Create by DbL on 2020/5/2 0002
*/
public class LHandlerMapping {
private Pattern pattern; // URL
private Method method; // 對應的Method
private Object controller; // Method對應的實例對象
public LHandlerMapping(Pattern pattern, Object controller, Method method) {
this.pattern = pattern;
this.method = method;
this.controller = controller;
}
public Pattern getPattern() {
return pattern;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
}
LModelAndView
package com.liulin.spring.framework.webmvc.servlet;
import java.util.Map;
/**
* Create by DbL on 2020/5/1 0001
*/
public class LModelAndView {
private String viewName;
private Map<String,?> model;
public LModelAndView(String viewName, Map<String, ?> model) {
this.viewName = viewName;
this.model = model;
}
public LModelAndView(String viewName) {
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public Map<String, ?> getModel() {
return model;
}
}
LView
package com.liulin.spring.framework.webmvc.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Create by DbL on 2020/5/2 0002
*/
public class LView {
private File viewFile;
public LView(File templateFile) {
this.viewFile = templateFile;
}
public void render(Map<String,?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception {
StringBuffer sb = new StringBuffer();
RandomAccessFile ra = new RandomAccessFile(this.viewFile,"r");
String line = null;
while(null != (line = ra.readLine())){
line = new String(line.getBytes("ISO-8859-1"),"utf-8");
Pattern pattern = Pattern.compile("¥\\{[^\\}]+\\}",Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(line);
while (matcher.find()){
String paramName = matcher.group();
paramName = paramName.replaceAll("¥\\{|\\}","");
Object paramValue = model.get(paramName);
line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
matcher = pattern.matcher(line);
}
sb.append(line);
}
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(sb.toString());
}
//處理特殊字符
public static String makeStringForRegExp(String str) {
return str.replace("\\", "\\\\").replace("*", "\\*")
.replace("+", "\\+").replace("|", "\\|")
.replace("{", "\\{").replace("}", "\\}")
.replace("(", "\\(").replace(")", "\\)")
.replace("^", "\\^").replace("$", "\\$")
.replace("[", "\\[").replace("]", "\\]")
.replace("?", "\\?").replace(",", "\\,")
.replace(".", "\\.").replace("&", "\\&");
}
}
LViewResolver
package com.liulin.spring.framework.webmvc.servlet;
import java.io.File;
/**
* Create by DbL on 2020/5/2 0002
*/
public class LViewResolver {
private final String DEFAULT_TEMMPLATE_SUFFX = ".html";
private File templateRootDir;
public LViewResolver(String templateRoot) {
String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
templateRootDir = new File(templateRootPath);
}
public LView resolverViewName(String viewName) {
if (null == viewName || "".equals(viewName.trim())) {
return null;
}
viewName = viewName.endsWith(DEFAULT_TEMMPLATE_SUFFX) ? viewName : (viewName + DEFAULT_TEMMPLATE_SUFFX);
File templateFile = new File((templateRootDir.getPath()+"/"+viewName).replaceAll("/+","/"));
return new LView(templateFile);
}
}
application.properties
scanPackage=com.liulin.demo
templateRoot=layouts