自己寫個Spring MVC
Spring mvc流程圖:
請求流程:
⑴ 使用者傳送請求至前端控制器DispatcherServlet
⑵ DispatcherServlet收到請求呼叫HandlerMapping處理器對映器。
⑶ 處理器對映器根據請求url找到具體的處理器,生成處理器物件及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。
⑷ DispatcherServlet通過HandlerAdapter處理器介面卡呼叫處理器
⑸ 執行處理器(Controller,也叫後端控制器)。
⑹ Controller執行完成返回ModelAndView
⑺ HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet
⑻ DispatcherServlet將ModelAndView傳給ViewReslover檢視解析器
⑼ ViewReslover解析後返回具體View
⑽ DispatcherServlet對View進行渲染檢視(即將模型資料填充至檢視中)。
⑾ DispatcherServlet響應使用者。從上面可以看出,DispatcherServlet有接收請求,響應結果,轉發等作用。有了DispatcherServlet之後,可以減少元件之間的耦合度。
DispatcherServlet類結構圖:
主要元件:
protected void initStrategies(ApplicationContext context) { //用於處理上傳請求。處理方法是將普通的request包裝成 MultipartHttpServletRequest,後者可以直接呼叫getFile方法獲取File. initMultipartResolver(context); //SpringMVC主要有兩個地方用到了Locale:一是ViewResolver檢視解析的時候;二是用到國際化資源或者主題的時候。 initLocaleResolver(context); //用於解析主題。SpringMVC中一個主題對應 一個properties檔案,裡面存放著跟當前主題相關的所有資源、//如圖片、css樣式等。SpringMVC的主題也支援國際化, initThemeResolver(context); //用來查詢Handler的。 initHandlerMappings(context); //從名字上看,它就是一個介面卡。Servlet需要的處理方法的結構卻是固定的,都是以request和response為引數的方法。//如何讓固定的Servlet處理方法呼叫靈活的Handler來進行處理呢?這就是HandlerAdapter要做的事情 initHandlerAdapters(context); //其它元件都是用來幹活的。在幹活的過程中難免會出現問題,出問題後怎麼辦呢?//這就需要有一個專門的角色對異常情況進行處理,在SpringMVC中就是HandlerExceptionResolver。 initHandlerExceptionResolvers(context); //有的Handler處理完後並沒有設定View也沒有設定ViewName,這時就需要從request獲取ViewName了,//如何從request中獲取ViewName就是RequestToViewNameTranslator要做的事情了。 initRequestToViewNameTranslator(context); //ViewResolver用來將String型別的檢視名和Locale解析為View型別的檢視。//View是用來渲染頁面的,也就是將程式返回的引數填入模板裡,生成html(也可能是其它型別)檔案。 initViewResolvers(context); //用來管理FlashMap的,FlashMap主要用在redirect重定向中傳遞引數。 initFlashMapManager(context); }
接下來實現mvc
1.新建一個web專案,web.xml裡宣告自定義的DispatcherServlet以及配置檔案
<?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/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <servlet> <servlet-name>MySpringMVC</servlet-name> <servlet-class>com.uiao.servlet.MyDispatcherServlet</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>MySpringMVC</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
application.properties裡只配置了掃描路徑
scanPackage=com.uiao.core
2.先來自定義幾個註解模仿Comtroller,RequestMapping,RequestParam註解
/**
* 控制層註解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
/**
* 方法路徑註解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
/**
* 引數註解
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value();
}
3.自定義DispatcherServlet類
/**
* 自定義DispatcherServlet
*/
public class MyDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<>();
private Map<String, Object> ioc = new HashMap<>();
private Map<String, Method> handlerMapping = new HashMap<>();
private Map<String, Object> controllerMapping = new HashMap<>();
@Override
public void init(ServletConfig config) {
//載入配置檔案進properties
doloadConfig(config.getInitParameter("contextConfigLocation"));
//掃描使用者包下面所有的類放到classNames裡
doScanner(properties.getProperty("scanPackage"));
//將掃描到的類通過反射例項化,並存放進IOC容器內
doInstance();
//將url和對映存進handlerMapping和controllerMapping
initHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
resp.getWriter().write("500 Server Exception");
}
}
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (handlerMapping.isEmpty()) {
return;
}
String url = request.getRequestURI();
String contextPath = request.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
if (!this.handlerMapping.containsKey(url)) {
response.getWriter().write("404 not found");
}
Method method = this.handlerMapping.get(url);
//獲取方法的引數列表
Class<?>[] parameterTypes = method.getParameterTypes();
//獲取請求的引數列表(只有這一個)
Map<String, String[]> parameterMap = request.getParameterMap();
//儲存引數值
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")) {
paramValues[i] = request;
continue;
}
if (requestParam.equals("HttpServletResponse")) {
paramValues[i] = response;
continue;
}
if (requestParam.equals("String")) {
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("[|]", "").replaceAll(",", ",");
paramValues[i] = value;
}
}
}
try {
method.invoke(this.controllerMapping.get(url), paramValues);//第一個引數為method所對應的例項,在ioc容器中
} catch (Exception e) {
e.printStackTrace();
}
}
private void doloadConfig(String location) {
InputStream inputStream = this.getClass().getResourceAsStream(location);
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void doScanner(String packageName) {
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll(".", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
doScanner(packageName + "." + file.getName());
} else {
String className = packageName + "." + file.getName().replaceAll(".class", "");
classNames.add(className);
}
}
}
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)) {
ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
} else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
private void initHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
try {
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<? extends Object> clazz = entry.getValue().getClass();
if (clazz.isAnnotationPresent(MyController.class)) {
continue;
}
String baseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class))//用在類上或直接用在方法上
{
MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = annotation.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(MyRequestMapping.class)) {//用在方法上
continue;
}
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String url = annotation.value();
url = (baseUrl + "/" + url).replace("/+", "/");
handlerMapping.put(url, method);
controllerMapping.put(url, clazz.newInstance());
System.out.println(url + "," + method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 把字串的首字母小寫
*
* @param name
* @return
*/
private String toLowerFirstWord(String name) {
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
4.寫個請求測試
@MyController
@MyRequestMapping("/test")
public class TestController {
@MyRequestMapping("/testa")
public void testA(HttpServletRequest request, HttpServletResponse response, @MyRequestParam("name") String name) {
System.out.println(name);
try {
response.getWriter().write("dotest methoda success ! param:" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
@MyRequestMapping("/testb")
public void testB(HttpServletRequest request, HttpServletResponse response) {
try {
response.getWriter().write("dotestb method success ! ");
} catch (Exception e) {
e.printStackTrace();
}
}
}