仿照SpringMVC,實現一個輕量級MVC框架,知識涉及到了反射機制、註解的使用和一些第三方工具包的使用java
主要的整體流程以下圖所示web
和以前同樣,咱們定義了一個DispatchServlet,用於攔截請求(這裏通常攔截.do結尾的url請求);ajax
以後,DispatchServlet會根據url,找到Controller中對應的方法並執行,返回一個結果。apache
咱們根據返回的結果,來DispatchServlet執行不一樣的操做(請求轉發、頁面重定向、返回json數據)json
看完這裏,整體的思路應該很明確了,問題來了:數組
對於第一個問題,咱們可使用註解來實現session
Controller
註解,用來標記Controller類RequestMapping
註解,用來標記url匹配的方法對於第二個問題,咱們能夠設置這樣的一套規則:mvc
redirect:
開頭,則代表DispatchServlet要執行頁面重定向操做redirect:
開頭,則代表DispatchServlet要執行請求轉發操做通常咱們會讓doGet方法也調用doPost方法,因此咱們只須要重寫Servlet中的doPost方法app
由上面的思路,咱們能夠獲得細化的流程圖(也就是doPost方法中的流程)框架
//得到url地址 String servletPath = req.getServletPath(); String requestUrl = StringUtils.substringBefore(servletPath,".do");
以前有說過,咱們定義了兩個註解,一個是Controller和RequestMapping
標記一個類和方法
@Controller public class UserController{ @RequestMapping("/user/login") public String login(HttpServletRequest request){ return "login.jsp"; } ... }
以後咱們就可使用Lang3
開源庫去查找類中是否有Controller
標記,而後再去尋找是否被RequestMapping標記的方法,並把RequestMapping註解上的標記的url、Controller全類名和方法名存起來
這裏我是用來一個類ClassMapping
來存放全類名和方法名,以後,使用url做爲key,ClassMapping
對象做爲value,存入一個HashMap中
這裏雖然也能夠放在doPost方法中,可是會形成資源的浪費,由於doPost方法可能會被執行屢次。因此,更好的作法是,能夠放在Servlet的初始化方法init()
裏面,這樣,只會執行一次。
我封裝了一個配置類Configuration
,專門用來查找url及其對應的方法
判斷類是否包含有Controller註解
controllerClass.isAnnotationPresent(Controller.class)
得到類中包含有RequestMapping註解的方法列表
Method[] methods = MethodUtils.getMethodsWithAnnotation(controllerClass, RequestMapping.class, true, true);
更多代碼請看下面的代碼部分Configuration類
先得到方法的參數列表類型,以後,把對象轉入到Object數組中,反射調用方法
避免錯誤,以前還需有個判空操做和是否包含有key,下面的貼出的代碼就省略了
ClassMapping classMapping = classMappingMap.get(requestUrl); Class<?> controllerClass = classMapping.getControllerClass(); Method method = classMapping.getMethod(); //得到方法的參數類型列表,以後根據此類型列表,對request傳來的參數進行處理 Class<?>[] parameterTypes = method.getParameterTypes(); //存放着以後須要傳入到方法的參數值 Object[] paramValues = new Object[parameterTypes.length]; //對request傳來的參數進行處理,將參數傳給controller中的對應的方法,調用該方法 try { //實例化controller類 Object o = controllerClass.newInstance(); for (int i = 0; i < parameterTypes.length; i++) { //這裏咱們只考慮了四種狀況,因此Controller種的方法參數類型也只有四種 if (ClassUtils.isAssignable(parameterTypes[i], HttpServletRequest.class)) { paramValues[i] = req; } else if (ClassUtils.isAssignable(parameterTypes[i], HttpServletResponse.class)) { paramValues[i] = resp; } else if (ClassUtils.isAssignable(parameterTypes[i], HttpSession.class)) { paramValues[i] = req.getSession(true); } else { //轉爲JavaBean if (parameterTypes[i] != null) { //得到request傳來的參數值,轉爲javabean Map<String, String[]> parameterMap = req.getParameterMap(); //實例化這個JavaBean類型 Object bean = parameterTypes[i].newInstance(); //把數值快速轉爲bean BeanUtils.populate(bean, parameterMap); paramValues[i] =bean; } } } //調用方法,得到返回值,根據返回值,執行頁面跳轉或者是返回json數據等操做 Object returnValue = MethodUtils.invokeMethod(o, true,method.getName(), paramValues);
這裏使用Google的gson框架,用於把實體類或者是List轉爲json數據
if (returnValue instanceof String) { String value = (String) returnValue; if (value.startsWith("redirect:")) { //重定向 resp.sendRedirect(req.getContextPath()+ StringUtils.substringAfter(value,"redirect:")); } else { //請求轉發 req.getRequestDispatcher(value).forward(req,resp); } } else { //返回一個對象 if (returnValue != null) { //轉爲json,ajax操做 String json = new Gson().toJson(o); resp.getWriter().print(json); }
不要忘了配置Servlet,和以前的Servlet同樣,可使用配置web.xml或者是註解方式進行配置
在方法RequestMapping上的註解上的須要有"/開頭",網頁的請求url不須要"/"開頭,可是須要.do結尾
因爲咱們只實現了四種類型的封裝,因此Controller類中裏面的方法參數只能是四種類型,request、response、session、一個JavaBean類
package mvc; import com.google.gson.Gson; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.log4j.Logger; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @author StarsOne * @date Create in 2019/8/9 0009 10:11 * @description */ public class DispatcherServlet extends HttpServlet { private Map<String, ClassMapping> classMappingMap =null; private Logger logger = Logger.getLogger(DispatcherServlet.class); @Override public void init(ServletConfig config) throws ServletException { super.init(config); //在servlet的初始化得到map數據 classMappingMap = new Configuration().config(); } @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 { //得到url地址 String servletPath = req.getServletPath(); String requestUrl = StringUtils.substringBefore(servletPath,".do"); //根據url地址去和map中的匹配,找到對應的controller類以及方法,以後反射傳參調用 if (classMappingMap != null && classMappingMap.containsKey(requestUrl)) { ClassMapping classMapping = classMappingMap.get(requestUrl); Class<?> controllerClass = classMapping.getControllerClass(); Method method = classMapping.getMethod(); //得到方法的參數類型列表,以後根據此類型列表,對request傳來的參數進行處理 Class<?>[] parameterTypes = method.getParameterTypes(); //存放着以後須要傳入到方法的參數值 Object[] paramValues = new Object[parameterTypes.length]; //對request傳來的參數進行處理,將參數傳給controller中的對應的方法,調用該方法 try { //實例化controller類 Object o = controllerClass.newInstance(); for (int i = 0; i < parameterTypes.length; i++) { if (ClassUtils.isAssignable(parameterTypes[i], HttpServletRequest.class)) { paramValues[i] = req; } else if (ClassUtils.isAssignable(parameterTypes[i], HttpServletResponse.class)) { paramValues[i] = resp; } else if (ClassUtils.isAssignable(parameterTypes[i], HttpSession.class)) { paramValues[i] = req.getSession(true); } else { //轉爲JavaBean if (parameterTypes[i] != null) { //得到request傳來的參數值,轉爲javabean Map<String, String[]> parameterMap = req.getParameterMap(); //實例化這個JavaBean類型 Object bean = parameterTypes[i].newInstance(); //把數值快速轉爲bean BeanUtils.populate(bean, parameterMap); paramValues[i] =bean; } } } //調用方法,得到返回值,根據返回值,執行頁面跳轉或者是返回json數據等操做 Object returnValue = MethodUtils.invokeMethod(o, true,method.getName(), paramValues); if (returnValue instanceof String) { String value = (String) returnValue; if (value.startsWith("redirect:")) { //重定向 resp.sendRedirect(req.getContextPath()+ StringUtils.substringAfter(value,"redirect:")); } else { //請求轉發 req.getRequestDispatcher(value).forward(req,resp); } } else { //返回一個對象 if (returnValue != null) { //轉爲json,ajax操做 String json = new Gson().toJson(o); resp.getWriter().print(json); } } } catch (InstantiationException e) { logger.error("實例化Controller對象錯誤!"); } catch (IllegalAccessException e) { logger.error("非法訪問方法!"); } catch (InvocationTargetException e) { logger.error("invocation target exception"); } catch (NoSuchMethodException e) { logger.error("調用的方法不存在!"); } } else { throw new RuntimeException("url不存在" + requestUrl); } } }
用來存放全類名和方法名
package mvc; import java.lang.reflect.Method; /** * 類Class和對應的方法Method * @author StarsOne * @date Create in 2019/8/8 0008 22:13 * @description */ public class ClassMapping { private Class<?> controllerClass; private Method method; public ClassMapping(Class<?> controllerClass, Method method) { this.controllerClass = controllerClass; this.method = method; } public Class<?> getControllerClass() { return controllerClass; } public void setControllerClass(Class<?> controllerClass) { this.controllerClass = controllerClass; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } @Override public String toString() { return controllerClass.getName() + "." + method.getName(); } }
package mvc; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.log4j.Logger; import java.io.File; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.ResourceBundle; import mvc.Annotation.Controller; import mvc.Annotation.RequestMapping; /** * @author StarsOne * @date Create in 2019/8/8 0008 22:11 * @description */ public class Configuration { Logger logger = Logger.getLogger(Configuration.class); private String getControllerPackage() { return ResourceBundle.getBundle("package").getString("packagePath"); } public Map<String, ClassMapping> config() { Map<String, ClassMapping> classMappingMap = Collections.synchronizedMap(new HashMap<>()); try { //根據資源文件中定義的包名,找到控制器的文件夾,獲得類名 File file = new File(getClass().getResource("/").toURI()); String controllerPackage = getControllerPackage(); String controllerFullPath = file.getPath() + File.separator +controllerPackage.replaceAll("\\.",File.separator); //controller類所在的文件夾 file = new File(controllerFullPath); //過濾文件,只找class文件 String[] classNames = file.list((dir, name) -> name.endsWith(".class")); for (String className : classNames) { //拼接全類名 String controllerFullName = controllerPackage + "." + StringUtils.substringBefore(className,".class"); Class controllerClass = ClassUtils.getClass(controllerFullName); //類是否有controller註解 if (controllerClass.isAnnotationPresent(Controller.class)) { //找到controller類中標明RequestMapping註解的全部方法 Method[] methods = MethodUtils.getMethodsWithAnnotation(controllerClass, RequestMapping.class, true, true); for (Method method : methods) { //得到註解上的路徑 String path = method.getAnnotation(RequestMapping.class).value(); //路徑爲key,保存 classMappingMap.put(path,new ClassMapping(controllerClass,method)); } } } } catch (URISyntaxException | ClassNotFoundException e) { e.printStackTrace(); } return classMappingMap; } public static void main(String[] args) { new Configuration().config(); } }
package mvc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author stars-one at 2019-08-09 08:50 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { String value() default ""; }
package mvc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author stars-one at 2019-08-09 08:50 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String value() default ""; }