一.手寫MVC框架
源碼位置:github網址
1.項目背景
因爲學校開始進行實訓,然後進行選題,大多數的都是商城了,管理系統了什麼的,這些項目大多數我都已經寫過了,實在是太無聊了,然後項目要求用原生的servlet進行實現。。。。emmmm實在是太麻煩了,寫一個請求就要創建一個servlet,所以我就想能不能自己實現一個類似於Dispatchservlet的servlet請求分發功能呢。因此該框架應運而生。
2.技術棧
Java基礎,Java反射,註解,servlet,JavaWeb,jsp。
3.功能實現
- 可以自動掃描所有的handler類。
- 可以進行請求分發。
- 可以進行自動實例化對象,就是控制反轉。
- 可以進行自動注入。
- 可以進行參數自動封裝。
- 可以進行Service層和Dao層的控制反轉。
- 可以通過註解改變bean的屬性和jsp以及sql的對應映射。
- 組件掃描
- 封裝參數後置處理器
- 封裝數據後置處理器
- json格式轉換。
- 視圖解析器
- 可以根據原生的jdbc進行參數封裝。
4.註解以及功能介紹
- AutoInstantiate:自動注入註解,當一個屬性被標註了這個註解之後,表示這個屬性會被自動注入實例對象。但是必須是框架所管理的對象。
- Bean:應用在類上,表示這是一個Bean類,當作爲方法參數的時候,能夠進行自動封裝數據。當然可以不標註,標註效率會高一點。
- ColumnName:這個註解是標註在Bean屬性上面的,表示這個字段要和數據庫中的列名進行匹配。爲了更好的參數封裝。默認使用字段名。
- JspParameter:這個註解是標註在Bean屬性上面的,表示這個字段要和JSP界面中傳遞的參數進行匹配,爲了更好的參數封裝。默認使用字段名。
- WebModule:這個註解是標註在類上的,表示這個類是控制類。會被框架所管理。
- Service:這個註解是標註在類上的,表示這是Service層,會被框架所管理。
- Dao:這個註解是標註在類上的,表示這是Dao層,會被框架所管理。
- Module:這個註解是標註在類上的,表示這是一個框架組件,會被框架所管理。
- Param:這個註解是標註在方法參數上的,表示這個參數要從前臺數據拿值,必須標註,如果不標註的話不會進行自動參數封裝。
- ParamPostProcesser:標註在類上,參數綁定後置處理器,當對Bean對象綁定參數的時候可能會出現類型不匹配,這個我們可以在這個後置處理器中進行類型匹配和注入。當然我們對這個後置處理器提供了一個默認方法,就是我們可以改變對象的值(只有這一次的機會)。
- DataPostProcessor:標註在類上,數據庫參數綁定後置處理器,當我們後面需要擴展類型的比對的時候會調用這個接口, 比如說我們的屬性有了一個新的類型,並且這個類型還對應着數據庫中的類型*,我們就需要繼承這個接口,並重寫方法。還是提供了一次改變對象的機會。
- RequestMapping:就是handler映射。標註在方法上。
- ResponseBody:標註在方法上,表示這個方法不走視圖處理器,直接返回JSON數據。
- RestWebModule:標註在類上,類似於RestController表示當前類所有的方法全不走視圖處理器,直接返回JSON數據。
- ReturnPage:標註在類上,返回視圖的前綴和後綴。默認是/WEB-INF/jsp/xxx.jsp 可以進行更改。標註在方法上,表示當前方法的返回視圖的前綴和後綴。
5.代碼實現
1.MainServlet
-
功能:
- 註解掃描。
- 自動實例化
- 自動注入
- 請求映射
- 請求分發,視圖處理
- 參數綁定
-
代碼
package com.shy.servlet; import cn.hutool.json.JSONUtil; import com.shy.annotation.*; import com.shy.utils.ClassUtils; import com.shy.utils.MyUtils; import com.shy.utils.ParameterNameUtils; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; /** * @author 石皓巖 * @create 2020-06-15 17:43 * 描述: */ @WebServlet("/") public class MainServlet extends HttpServlet { /** * 用來保存映射關係的 * /student/list -> method */ private static Map<String, Method> methodMap = new ConcurrentHashMap<>(); /** * 保存所有的標註了WebModule註解的類 */ private static List<Class> webModuleClasses = new CopyOnWriteArrayList<>(); /** * 保存了方法和實例化對象的緩存 * method -> 這個方法的類的對象 */ private static Map<Method, Object> methodObjectMap = new ConcurrentHashMap<>(); /** * 實例化池 * Class -> 實例化對象 */ public static Map<Class, Object> singleMap = new ConcurrentHashMap<>(); /** * 默認返回界面的前綴和後綴 */ private final static String PREFIX = "/jsp/"; private final static String SUFFIX = ".jsp"; /** * 請求轉發 */ private final static String FORWARD = "forward"; /** * 請求重定向 */ private final static String REDIRECT = "redirect"; /** * 默認的錯誤界面 */ private final static String ERROR_PAGE = "/404.jsp"; /** * 這個用來保存request域中的參數 */ private final static Map<String, Object> REQUESTPARAM = new ConcurrentHashMap<>(); static { //1.掃描所有的包,並拿到該項目所有的類 List<Class> classes = scanAllModule(); //2.搞一個實例化緩存池,直接在下面的循環中實例化了。拿到所有的標註了WebModule的註解類 instantiationModule(classes); //3.拿到webModule類的所有方法 HandllerMapping(); //4.自動注入功能 autoInstantiation(); } private static void HandllerMapping() { for (Class webModuleClass : webModuleClasses) { Method[] methods = webModuleClass.getMethods(); //4.拿到方法上的所有註解RequestMapping的方法,並且放到一個map映射中 for (Method method : methods) { RequestMapping annotation = method.getAnnotation(RequestMapping.class); if (annotation != null) { //如果一個方法標註了RequestMapping註解,那就拿到它的值 String value = annotation.value(); //把當前方法和字符串進行映射 methodMap.put(value, method); try { methodObjectMap.put(method, singleMap.get(webModuleClass)); } catch (Exception e) { e.printStackTrace(); } } } } } private static void instantiationModule(List<Class> classes) { for (Class aClass : classes) { // 實例化所有的module if (aClass.isInterface()) { continue; } try { if (aClass.getAnnotation(WebModule.class) != null) { webModuleClasses.add(aClass); singleMap.put(aClass, aClass.newInstance()); } else if (aClass.getAnnotation(Service.class) != null) { singleMap.put(aClass, aClass.newInstance()); } else if (aClass.getAnnotation(Dao.class) != null) { singleMap.put(aClass, aClass.newInstance()); } else if (aClass.getAnnotation(Module.class) != null) { singleMap.put(aClass, aClass.newInstance()); } } catch (Exception ex) { ex.printStackTrace(); } } } /** * 自動實例化 */ private static void autoInstantiation() { Set<Class> classes1 = singleMap.keySet(); for (Class aClass : classes1) { // 拿到所有的屬性 Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { declaredField.setAccessible(true); // 判斷是否添加了自動注入 AutoInstantiate annotation = declaredField.getAnnotation(AutoInstantiate.class); if (annotation != null) { // 拿到這個屬性的類型 Class<?> type = declaredField.getType(); try { // 如果是接口的話,就要進行類型注入 if (type.isInterface()) { for (Class aClass1 : classes1) { Class[] interfaces = aClass1.getInterfaces(); for (Class anInterface : interfaces) { if (anInterface.equals(type)) { type = aClass1; } } } } declaredField.set(singleMap.get(aClass), singleMap.get(type)); } catch (Exception e) { e.printStackTrace(); } } } } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 設置編碼 setCharacterEncoding(request, response, "UTF-8"); //5.拿到請求的路徑,並找出相應的方法。 /student Method method = getRequestMappingMethod(request, response); //6.通過註解解析出方法的所有參數 Object[] args = getArgsAndBindParam(request, response, method); //9.執行方法 Object object = invokeMethodAndBindMap(request, method, args); //10.執行視圖解析 viewResolver(request, response, method, object); } private void viewResolver(HttpServletRequest request, HttpServletResponse response, Method method, Object object) throws IOException, ServletException { // 這一步是爲了判斷是否需要直接返回JSON串 if (!returnJson(response, method, object)) { // 如果沒有的話 直接進行跳轉界面 gotoPage(request, response, method, object); } } private boolean returnJson(HttpServletResponse response, Method method, Object object) throws IOException { ResponseBody annotation1 = method.getAnnotation(ResponseBody.class); Object o = methodObjectMap.get(method); RestWebModule annotation = o.getClass().getAnnotation(RestWebModule.class); if (annotation1 != null || annotation != null) { // 這裏需要通過json工具進行轉化 但是我沒弄 String s = JSONUtil.toJsonStr(object); response.setCharacterEncoding("GBK"); response.getWriter().write(s); return true; } return false; } private void setCharacterEncoding(HttpServletRequest request, HttpServletResponse response, String character) throws UnsupportedEncodingException { request.setCharacterEncoding(character); response.setCharacterEncoding(character); } /** * 如果沒加註解的話首先判斷是否是請求轉發和請求重定向 * * @param request * @param response * @param method 執行的方法 * @param object 執行方法的返回值 * @throws ServletException * @throws IOException */ private void gotoPage(HttpServletRequest request, HttpServletResponse response, Method method, Object object) throws ServletException, IOException { String invoke = String.valueOf(object); // 判斷一下是否直接跳轉界面 if (invoke.contains(":")) { String[] split = invoke.split(":"); if (split[0].equals(FORWARD)) { // 直接進行請求轉發 request.getRequestDispatcher(split[1]).forward(request, response); } if (split[0].equals(REDIRECT)) { response.sendRedirect(split[1]); } } else { // 都沒有的話直接進行默認跳轉界面 String path = PREFIX + invoke + SUFFIX; // 通過返回值跳轉界面,拼接字符串,進行跳轉界面 //首先確定一下當前方法是否配置了前綴和後綴 Object o = methodObjectMap.get(method); ReturnPage annotation1 = o.getClass().getAnnotation(ReturnPage.class); if (annotation1 != null) { path = annotation1.prefix() + invoke + annotation1.suffix(); } ReturnPage annotation = method.getAnnotation(ReturnPage.class); if (annotation != null) { path = annotation.prefix() + invoke + annotation.suffix(); } // 返回界面 request.getRequestDispatcher(path).forward(request, response); } } private Object invokeMethodAndBindMap(HttpServletRequest request, Method method, Object[] args) { Object o = methodObjectMap.get(method); // 我可以拿到返回值 Object object = null; try { object = method.invoke(o, args); } catch (Exception e) { e.printStackTrace(); } //這一步是判斷是否我們通過map添加了參數,如果添加了就放到request域中 if (REQUESTPARAM.size() > 0) { Set<String> objects = REQUESTPARAM.keySet(); Iterator<String> iterator = objects.iterator(); if (iterator.hasNext()) { String key = iterator.next(); request.setAttribute(key, REQUESTPARAM.get(key)); } } REQUESTPARAM.clear(); return object; } private Object[] getArgsAndBindParam(HttpServletRequest request, HttpServletResponse response, Method method) { // 當我們沒有參數的時候直接返回就行了 if (method.getParameterCount() == 0) { return null; } // 這裏會拿到所有的標註了@Param註解的參數 // 我們只要想通過參數拿簡單值,就需要標註這個註解 String[] methodParameterNames = ParameterNameUtils.getMethodParameterNamesByAnnotation(method); String value = null; Object[] args = new Object[method.getParameterCount()]; int i = 0; // 設置標註了註解的參數 for (String methodParameterName : methodParameterNames) { args[i] = request.getParameter(methodParameterName); i++; } // 封裝request和response還有map // 拿到所有的參數類型 Class<?>[] parameterTypes = method.getParameterTypes(); int j = 0; for (Class<?> parameterType : parameterTypes) { // 注入基本類型的屬性 if (MyUtils.isCommonType(parameterType)) { if (parameterType.equals(Integer.class) || parameterType.equals(int.class)) { args[j] = Integer.parseInt((String) args[j]); j++; } else if (parameterType.equals(Double.class) || parameterType.equals(double.class)) { args[j] = Double.parseDouble((String) args[j]); j++; } else if (parameterType.equals(Long.class) || parameterType.equals(long.class)) { args[j] = Long.parseLong((String) args[j]); j++; } } else if (parameterType.equals(HttpServletRequest.class)) { //注入request args[i] = request; i++; } else if (parameterType.equals(HttpServletResponse.class)) { //注入response args[i] = response; i++; } else if (parameterType.equals(HttpSession.class)) { //注入session args[i] = request.getSession(); i++; } else if (parameterType.equals(Map.class)) { //注入map args[i] = REQUESTPARAM; i++; } else if (MyUtils.isBean(parameterType)) { //如果是bean類型的話,自動注入 args[i] = MyUtils.autowriteParameter(request, parameterType); i++; } } return args; } private Method getRequestMappingMethod(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String servletPath = request.getServletPath(); Method method = methodMap.get(servletPath); if (method == null) { request.getRequestDispatcher(ERROR_PAGE).forward(request, response); } return method; } /** * 根據當前servlet的路徑掃描所有的servlet * * @param mainServletClass * @return */ private static List<Class> scanWebModule(Class<?> mainServletClass) { String packageName = mainServletClass.getName().substring(0, mainServletClass.getName().lastIndexOf(".")); Set<String> className = ClassUtils.getClassName(packageName, true); List<Class> classList = new ArrayList<>(); for (String s : className) { if (packageName.equals(s)) { continue; } Class<?> aClass = null; try { aClass = Class.forName(s); } catch (ClassNotFoundException e) { e.printStackTrace(); } classList.add(aClass); } return classList; } private static List<Class> scanAllModule() { Set<String> className = ClassUtils.getClassName("", true); List<Class> classList = new ArrayList<>(); for (String s : className) { Class<?> aClass = null; try { aClass = Class.forName(s.substring(1)); } catch (ClassNotFoundException e) { e.printStackTrace(); } classList.add(aClass); } return classList; } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
2.DataType
-
功能:
- 參數類型的枚舉類
-
代碼
package com.shy.myenum; /** * @author 石皓巖 * @create 2020-02-28 15:51 * 描述:數據類型 */ public enum DataType { /** * 封裝數據的時候。返回值是List集合 */ LIST, /** * 封裝數據的時候,返回值是單個對象 */ OBJECT, /** * 表類型 */ TABLE, /** * request中參數的類型,用來封裝數據的 */ REQUEST }
3.ClassUtils
-
功能:
- Class類型掃描,返回class類。
-
代碼
package com.shy.utils; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * @author 石皓巖 * @create 2020-06-15 21:58 * 描述: */ public class ClassUtils { public static void main(String[] args) throws Exception { String packageName = ""; Set<String> classNames = getClassName(packageName, true); if (classNames != null) { for (String className : classNames) { System.out.println(className); } } } /** * 獲取某包下所有類 * @param packageName 包名 * @param isRecursion 是否遍歷子包 * @return 類的完整名稱 */ public static Set<String> getClassName(String packageName, boolean isRecursion) { Set<String> classNames = null; ClassLoader loader = Thread.currentThread().getContextClassLoader(); String packagePath = packageName.replace(".", "/"); URL url = loader.getResource(packagePath); if (url != null) { String protocol = url.getProtocol(); if (protocol.equals("file")) { classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion); } else if (protocol.equals("jar")) { JarFile jarFile = null; try{ jarFile = ((JarURLConnection) url.openConnection()).getJarFile(); } catch(Exception e){ e.printStackTrace(); } if(jarFile != null){ getClassNameFromJar(jarFile.entries(), packageName, isRecursion); } } } else { /*從所有的jar包中查找包名*/ classNames = getClassNameFromJars(((URLClassLoader)loader).getURLs(), packageName, isRecursion); } return classNames; } /** * 從項目文件獲取某包下所有類 * @param filePath 文件路徑 * @param className 類名集合 * @param isRecursion 是否遍歷子包 * @return 類的完整名稱 */ private static Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) { Set<String> className = new HashSet<String>(); File file = new File(filePath); File[] files = file.listFiles(); for (File childFile : files) { if (childFile.isDirectory()) { if (isRecursion) { className.addAll(getClassNameFromDir(childFile.getPath(), packageName+"."+childFile.getName(), isRecursion)); } } else { String fileName = childFile.getName(); if (fileName.endsWith(".class") && !fileName.contains("$")) { className.add(packageName+ "." + fileName.replace(".class", "")); } } } return className; } /** * @param jarEntries * @param packageName * @param isRecursion * @return */ private static Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion){ Set<String> classNames = new HashSet<String>(); while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); if(!jarEntry.isDirectory()){ /* * 這裏是爲了方便,先把"/" 轉成 "." 再判斷 ".class" 的做法可能會有bug * (FIXME: 先把"/" 轉成 "." 再判斷 ".class" 的做法可能會有bug) */ String entryName = jarEntry.getName().replace("/", "."); if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) { entryName = entryName.replace(".class", ""); if(isRecursion){ classNames.add(entryName); } else if(!entryName.replace(packageName+".", "").contains(".")){ classNames.add(entryName); } } } } return classNames; } /** * 從所有jar中搜索該包,並獲取該包下所有類 * @param urls URL集合 * @param packageName 包路徑 * @param isRecursion 是否遍歷子包 * @return 類的完整名稱 */ private static Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) { Set<String> classNames = new HashSet<String>(); for (int i = 0; i < urls.length; i++) { String classPath = urls[i].getPath(); //不必搜索classes文件夾 if (classPath.endsWith("classes/")) {continue;} JarFile jarFile = null; try { jarFile = new JarFile(classPath.substring(classPath.indexOf("/"))); } catch (IOException e) { e.printStackTrace(); } if (jarFile != null) { classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion)); } } return classNames; } }
4.DataBindingUtils
-
功能:
- request參數綁定。支持駝峯命名,支持下劃線命名、模糊大小寫、支持註解微調。
- mysql數據庫參數綁定。支持駝峯命名,支持下劃線命名、模糊大小寫、支持註解微調。例如:creatTime = creattime = creat_name
- bean參數綁定支持,級聯綁定,比如說Student對象中包含Teacher對象,Teacher對象中包含Project對象。支持全部參數綁定。
-
代碼
package com.shy.utils; import cn.hutool.core.bean.BeanUtil; import com.shy.annotation.*; import com.shy.myenum.DataType; import com.shy.servlet.MainServlet; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author 石皓巖 * @create 2020-02-28 18:38 * 描述:該工具類是我寫的一個完成自動裝配的功能包 */ @SuppressWarnings("all") public class DataBindingUtils { //所有的屬性保存在這裏 private static Map<String, Field> allFields = new ConcurrentHashMap<>(); //因爲最後需要進行一些字符串比對,會經過一些轉換 //這個裏面維護了原始字符串 關係:轉換後的->原始字符串 private static Map<String, String> alias = new ConcurrentHashMap<>(); // 這個是維護bean類型的映射表 private static Map<String, Map<String, Field>> beanMap = new ConcurrentHashMap<>(); private static void cuttingParameters(Map<String, Object> map, String s1) { String[] split1 = s1.split("="); if (split1.length == 2) { String name = split1[0]; String value = split1[1]; //進行字符串處理 name = getNotUnderlineString(name); map.put(name, value); } else if (split1.length == 1) { String name = split1[0]; map.put(name, ""); } } /** * 自動封裝參數,我們可以自己調用,但是也可以不用,因爲我們自己進行調用了,當我們調用的方法有參數的時候, * 會調用這裏進行自動封裝數據 * * @param request * @param dataType * @param <T> * @return */ public static <T> T autowriteParameter(HttpServletRequest request, Class dataType) { if (request == null) { throw new RuntimeException("request不能爲空"); } if (dataType == null) { throw new RuntimeException("class類型不能爲空"); } try { // 得到對象並且會把Field進行映射 Object o = getObjectAndIntegrateMap(dataType, DataType.REQUEST); // 設置常見類型 setCommonParam(o, request); // 設置bean類型的參數 setBeanParam(o, request); return (T) o; } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * 設置通用參數,private 不允許其他人調用,用來從reuqest中獲取參數並設置 * * @param o * @param request */ private static void setCommonParam(Object o, HttpServletRequest request) { Set<String> keySet1 = allFields.keySet(); Iterator<String> iterator = keySet1.iterator(); while (iterator.hasNext()) { String key = iterator.next(); Field field = allFields.get(key); String value = request.getParameter(key); if (isCommonType(field.getType())) { setCommonType(o, value, field); iterator.remove(); } } } /** * 設置bean參數,private 不允許其他人調用,用來從reuqest中獲取參數並設置 * * @param o * @param request */ private static void setBeanParam(Object o, HttpServletRequest request) throws InstantiationException, IllegalAccessException { Set<String> keySet = allFields.keySet(); Iterator<String> iterator = keySet.iterator(); while (iterator.hasNext()) { String name = iterator.next(); Field field = allFields.get(name); Object o1 = field.getType().newInstance(); if (beanMap.containsKey(name)) { Map<String, Field> stringFieldMap = beanMap.get(name); Set<String> keySet1 = stringFieldMap.keySet(); Iterator<String> iterator1 = keySet1.iterator(); while (iterator1.hasNext()) { String next = iterator1.next(); String parameter = request.getParameter(next); if (parameter == null) { continue; } if (stringFieldMap.containsKey(next)) { Field child = stringFieldMap.get(next); setCommonType(o1, parameter, child); iterator1.remove(); } } field.set(o, o1); iterator.remove(); beanMap.remove(name); } } } private static Object getObjectAndIntegrateMap(Class dataType, DataType tableOrJsp) throws InstantiationException, IllegalAccessException { allFields.clear(); alias.clear(); //拿到這個對象的所有屬性 Object o = dataType.newInstance(); Field[] all = getAllFields(dataType); //把所有的屬性整合到一起 //裏面涉及到了轉換字符串的算法,就是把所有的字符串轉換成小寫 //如果有_就刪除掉 doTransFormMap(all, tableOrJsp); return o; } private static Field[] getAllFields(Class dataType) { Field[] declaredFields = dataType.getDeclaredFields(); Field[] declaredFields1 = dataType.getSuperclass().getDeclaredFields(); Field[] all = new Field[declaredFields.length + declaredFields1.length]; System.arraycopy(declaredFields, 0, all, 0, declaredFields.length); System.arraycopy(declaredFields1, 0, all, declaredFields.length, declaredFields1.length); return all; } /** * 我自己寫的封裝數據的方法他會根據你傳遞進來的rs進行遍歷尋找出查詢出來的數據, * class是將要封裝成的對象 * dataType是將要返回值的類型 支持List和Object * 最後一個參數是後置處理器 * * @param rs * @param * @param * @return */ public static <T> T autowireData(ResultSet rs, Class clazz, DataType dataType) { if (rs == null) { throw new RuntimeException("ResultSet不能爲空"); } if (clazz == null) { throw new RuntimeException("class類型不能爲空"); } //用來保存數據的 List data = new ArrayList<>(); //遍歷數據 try { while (rs.next()) { //創建實例 Object o = DataBindingUtils.getObjectAndIntegrateMap(clazz, DataType.TABLE); //拿到所有的rs中的列明 //進行自動裝配 doAssembly(rs, o); //裝入list data.add(o); } } catch (Exception e) { throw new RuntimeException("數據封裝失敗,請檢查將要封裝的類型和查詢出來的類型是否一致"); } if (dataType == null || dataType.equals(DataType.OBJECT)) { if (data.size() == 0) { return null; } return (T) data.get(0); } else if (dataType.equals(DataType.LIST)) { if (data.size() == 0) { return null; } return (T) data; } return null; } private static void doAssembly(ResultSet rs, Object o) throws SQLException, IllegalAccessException { ResultSetMetaData metaData = rs.getMetaData(); int i = 1; int temp; for (; i <= metaData.getColumnCount(); i++) { //這一步拿到原值,並且原值和轉換後的值做了映射放到了alias這個別名集合中 String columnName = getSimpleColumnName(metaData.getColumnName(i)); // 直接封裝普通對象 if (allFields.containsKey(columnName)) { Field field = allFields.get(columnName); setCommonType(o, String.valueOf(rs.getObject(alias.get(columnName))), field); //我給你一次對每一列重新匹配的機會,並且給你重新改變對象的機會 invokePostProcesser(rs, o, columnName, field); allFields.remove(columnName); alias.remove(columnName); } else { try { temp = i; Iterator<String> iterator1 = beanMap.keySet().iterator(); while (iterator1.hasNext()) { i = temp; Object child = null; String beanName = iterator1.next(); if (allFields.containsKey(beanName)) { // 這個是teacher的filed Field field = allFields.get(beanName); if (child == null) { child = field.getType().newInstance(); } Map<String, Field> stringFieldMap = beanMap.get(beanName); for (; i <= metaData.getColumnCount(); i++) { columnName = getSimpleColumnName(metaData.getColumnName(i)); if (stringFieldMap.containsKey(columnName)) { Field childField = stringFieldMap.get(columnName); if (rs.getObject(columnName) == null) { continue; } setCommonType(child, String.valueOf(rs.getObject(alias.get(columnName))), childField); stringFieldMap.remove(columnName); alias.remove(columnName); } else { beanMap.remove(beanName); allFields.remove(beanName); alias.remove(columnName); } } field.set(o, child); } } } catch (Exception ex) { ex.printStackTrace(); } } } } private static String getSimpleColumnName(String name) { String columnName = name.toLowerCase(); //進行字符串處理,就是得到去掉下劃線的列名 columnName = getNotUnderlineString(columnName); alias.put(columnName, name); return columnName; } private static void setCommonType(Object o, String value, Field field) { try { if (value == null) { return; } else if (field.getType().equals(String.class)) { field.set(o, value); } else if (field.getType().equals(int.class) || field.getType().equals(Integer.class)) { field.set(o, Integer.parseInt(value)); } else if (field.getType().equals(double.class) || field.getType().equals(Double.class)) { field.set(o, Double.parseDouble(value)); } else if (field.getType().equals(float.class) || field.getType().equals(Float.class)) { field.set(o, Float.parseFloat(value)); } else if (field.getType().equals(Long.class) || field.getType().equals(long.class)) { field.set(o, Long.parseLong(value)); } } catch (Exception ex) { ex.printStackTrace(); } } private static void invokePostProcesser(ResultSet rs, Object o, String columnName, Field field) { Set<Class> classes = MainServlet.singleMap.keySet(); for (Class aClass : classes) { if (aClass.getInterfaces().length > 0 && aClass.getInterfaces()[0].equals(DataPostProcessor.class)) { DataPostProcessor dataPostProcessor = (DataPostProcessor) MainServlet.singleMap.get(aClass); dataPostProcessor.typeOfContrast(rs, field, o, columnName); dataPostProcessor.changeObject(o); } } } private static void doTransFormMap(Field[] all, DataType dataType) { for (Field field : all) { field.setAccessible(true); // 我想當解析每個類型的時候,手首先進行一個類型判斷 if (!isCommonType(field.getType())) { String name = getCommonFieldName(field, dataType); Map<String, Field> map = new ConcurrentHashMap<>(); Field[] all1 = getAllFields(field.getType()); doTransFormMap(all1, dataType); for (Field field1 : all1) { field1.setAccessible(true); String childName = getCommonFieldName(field1, dataType); map.put(childName, field1); allFields.put(name, field); } beanMap.put(name, map); } else { String name = getCommonFieldName(field, dataType); //這個是爲了防止父類的屬性覆蓋 if (!allFields.containsKey(name)) { allFields.put(name, field); } } } } private static String getCommonFieldName(Field field, DataType dataType) { String name = field.getName().toLowerCase(); //接下來需要處理註解,如果定義了註解就需要進行解析註解 if (dataType == null || dataType.equals(DataType.TABLE)) { ColumnName annotation = field.getAnnotation(ColumnName.class); if (annotation != null && !annotation.value().equals("")) { name = annotation.value().toLowerCase(); } } else if (dataType.equals(DataType.REQUEST)) { JspParameter annotation = field.getAnnotation(JspParameter.class); if (annotation != null && !annotation.value().equals("")) { name = annotation.value().toLowerCase(); } } //轉換爲沒有下劃線的字符串 name = getNotUnderlineString(name); return name; } /** * 抽取出來的方法,得到沒有下劃線的字符串 * * @param name * @return */ public static String getNotUnderlineString(String name) { //首先驗證參數,name不能爲空否則拋出 異常 if (name == null) { throw new RuntimeException("當轉換字符串的時候,name不能爲空"); } if (name.contains("_")) { String[] s = name.split("_"); name = ""; for (String s1 : s) { name += s1; } } return name; } public static boolean isBean(Class<?> parameterType) { /*//如果是基本類型直接返回 if (isCommonType(parameterType)) { return false; } // 怎麼判斷一個Class是bean類型呢? // 我覺得應該是我們對bean的定義吧,首先我覺得應該是對註解的判斷,如果添加了bean註解沒什麼好說的,直接就是了 if (parameterType.getAnnotation(Bean.class) != null) { return true; } boolean flag = false; // 2.我覺得應該拿到所有的屬性,判斷是否所有的屬性都私有 Field[] allFields = new Field[parameterType.getDeclaredFields().length + parameterType.getSuperclass().getDeclaredFields().length]; Field[] fields = parameterType.getDeclaredFields(); Field[] superFields = parameterType.getSuperclass().getDeclaredFields(); System.arraycopy(fields, 0, allFields, 0, fields.length); System.arraycopy(superFields, 0, allFields, fields.length, superFields.length); return isHasGetterAndSetter(parameterType, allFields);*/ return BeanUtil.isBean(parameterType); } public static boolean isCommonType(Class<?> parameterType) { if (parameterType.equals(String.class)) { return true; } else if (parameterType.equals(int.class) || parameterType.equals(Integer.class)) { return true; } else if (parameterType.equals(double.class) || parameterType.equals(Double.class)) { return true; } else if (parameterType.equals(float.class) || parameterType.equals(Float.class)) { return true; } else if (parameterType.equals(long.class) || parameterType.equals(Long.class)) { return true; } else if (parameterType.equals(short.class) || parameterType.equals(Short.class)) { return true; } else if (parameterType.equals(byte.class) || parameterType.equals(Byte.class)) { return true; } else if (parameterType.equals(char.class) || parameterType.equals(Character.class)) { return true; } else if (parameterType.equals(boolean.class) || parameterType.equals(Boolean.class)) { return true; } return false; } public static boolean isHasGetterAndSetter(Class<?> parameterType, Field[] fields) { boolean flag = false; for (Field field : fields) { if (field.isAccessible()) { break; } // 判斷是否存在getter/setter方法 String name = field.getName(); name = name.substring(0, 1).toUpperCase() + name.substring(1); String getter = "get" + name; String setter = "set" + name; try { if (parameterType.getMethod(getter) == null) { flag = false; break; } if (parameterType.getMethod(setter, field.getType()) == null) { flag = false; break; } } catch (NoSuchMethodException e) { e.printStackTrace(); } flag = true; } return flag; } }
5.ParameterNameUtils
-
功能
- 解析出方法中標註了@Param參數的value,然後其他的地方能進行自動參數注入。
-
代碼
package com.shy.utils; import com.shy.annotation.Param; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** * @author 石皓巖 * @create 2020-06-15 18:57 * 描述: */ public class ParameterNameUtils { /** * 獲取指定方法的參數名 * * @param method 要獲取參數名的方法 * @return 按參數順序排列的參數名列表 */ public static String[] getMethodParameterNamesByAnnotation(Method method) { Annotation[][] parameterAnnotations = method.getParameterAnnotations(); if (parameterAnnotations == null || parameterAnnotations.length == 0) { return null; } int num = 0; for (Annotation[] parameterAnnotation : parameterAnnotations) { for (Annotation annotation : parameterAnnotation) { Class<? extends Annotation> aClass = annotation.annotationType(); if (aClass == Param.class) { num++; } } } String[] parameterNames = new String[num]; int i = 0; for (Annotation[] parameterAnnotation : parameterAnnotations) { for (Annotation annotation : parameterAnnotation) { if (annotation instanceof Param) { Param param = (Param) annotation; parameterNames[i++] = param.value(); } } } return parameterNames; } }
6.後續功能展望以及實現思路
1.功能展望:
- dao層的處理,就是數據庫框架。
- 希望能支持配置文件的方式。
2.實現思路
dao層實現思路。
- 可以把所有的dao層定義接口形式的。
- 提供select、insert、update、delete註解進行數據庫的操作。
- 其實想一下,所有的數據庫執行步驟基本相同。
- 我們首先掃描出所有的DAO接口
- 然後通過動態代理的方式進行核心邏輯編寫。就是實現一個InvocationHandler這個接口然後編寫核心代碼。
- public Object invoke(Object proxy, Method method, Object[] args);方法是這樣的。
- 我們可以通過method拿到他的註解,然後我們定義幾個類用來分別處理select、insert、update、delete註解。
- 比如說select解析類,解析註解,解析出sql語句,我們還需要進行語法規定,比如說參數#{}可以獲取到參數。或者?,然後通過args參數進行封裝,因爲我們肯定把連接對象提前進行注入了,所以直接執行查詢語句,封裝參數就行了。
3.引發的問題
-
問:什麼時候,得到代理對象?
解決方案:當我們框架進行掃描類的時候,發現是dao註解標註的類型,直接進行保存。然後實例化的時候,我們直接根據類型獲取代理對象就行了。
二.手寫mybatis框架
1.項目背景
由於我們已經晚上了上面的所有操作,但是發現數據庫層次還是存在大量的重複代碼,於是我們想借鑑一下mybatis的設計,完全註解或者配置文件的方式進行數據庫查詢,所有的Dao層全是接口,不需要實現類,只需要標註一下註解就好了。
2.技術棧
Java基礎,Java反射,註解,jdbc,動態代理。
3.功能實現
- dao層標註@Dao註解,然後dao層就可以完全是接口了,但是我做了個擴展,就是我們直接寫dao的實現類也行。
- 標註select註解自動查詢和封裝返回數據
- 標註insert註解自動插入數據
- 標註update註解,自動更新數據。
- 標註delete註解,自動刪除數據。
- 標註NotQuery註解,這個相當於增強型功能,所有的非查詢方法都可以用這個,會自動進行各種操作。
- 參數匹配,支持bean類型的匹配。
4.註解以及功能介紹
- Dao:上面已經介紹過了,就是dao層的註解。
- Delete:刪除語句註解。
- Select:查詢語句註解
- Insert:插入語句註解
- Update:更新語句註解
- NotQuery註解,非查詢註解。
5.代碼實現
1.獲得代理對象
public class DaoProxy {
public static Object getProxy(Class interfaceClass, Connection connection){
// 直接代理對象
return Proxy.newProxyInstance(DaoProxy.class.getClassLoader(),
new Class[]{interfaceClass},
new RemoteInvocationHandler(interfaceClass,
connection));
}
}
2.代理核心邏輯
package com.shy.proxy;
import com.shy.annotation.*;
import com.shy.myinterface.CrudParse;
import com.shy.myinterface.impl.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
/**
* @author 石皓巖
* @create 2020-06-08 12:04
* 描述:
*/
public class RemoteInvocationHandler implements InvocationHandler {
private Class interfaceClass;
private Connection connection;
public RemoteInvocationHandler() {
}
public RemoteInvocationHandler(Class interfaceClass, Connection connection) {
this.interfaceClass = interfaceClass;
this.connection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
Object result = null;
if (method.getAnnotation(Select.class) != null) {
// 查詢
result = parse(new SelectParse(), proxy, method, args, connection);
} else if (method.getAnnotation(Insert.class) != null) {
// 插入
parse(new InsertParse(), proxy, method, args, connection);
} else if (method.getAnnotation(Update.class) != null) {
// 更新
parse(new UpdateParse(), proxy, method, args, connection);
} else if (method.getAnnotation(Delete.class) != null) {
// 更新
parse(new DeleteParse(), proxy, method, args, connection);
} else if (method.getAnnotation(NotQuery.class) != null) {
// 更新
parse(new NotQueryParse(), proxy, method, args, connection);
}
return result;
}
public Object parse(CrudParse crudParse, Object proxy, Method method, Object[] args, Connection connection) {
return crudParse.parse(proxy, method, args, connection);
}
}
3.查詢註解解析核心邏輯
package com.shy.myinterface.impl;
import com.shy.annotation.ReturnDataType;
import com.shy.annotation.Select;
import com.shy.myenum.DataType;
import com.shy.myinterface.CrudParse;
import com.shy.utils.DataBindingUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* @author 石皓巖
* @create 2020-06-22 15:12
* 描述:
*/
public class SelectParse implements CrudParse {
@Override
public Object parse(Object peoxy, Method method, Object[] args, Connection connection) {
Object result = null;
ResultSet rs = null;
try {
//先解析出select註解
Select annotation = method.getAnnotation(Select.class);
// 核心參數解析封裝邏輯
PreparedStatement ps = DataBindingUtils.parseParams(method, args, connection, annotation.value());
rs = ps.executeQuery();
// 封裝數據
result = getResult(method, result, rs, annotation);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/*
這裏是最終的封裝數據。。。。emmm做了優化,
你可以不必標註任何註解,所有的封裝了返回值處理了,
我全部給你做了
*/
private Object getResult(Method method,
Object result,
ResultSet rs,
Select annotation) {
if (annotation.resultDataType() != null &&
!annotation.resultDataType().equals(Object.class)) {
result = DataBindingUtils.autowireData(rs,
annotation.resultDataType(),
annotation.dataType());
} else if (method.getAnnotation(ReturnDataType.class) != null) {
result = DataBindingUtils.autowireData(rs, method.getAnnotation(ReturnDataType.class).value(),
annotation.dataType());
} else {
try {
if (method.getReturnType().equals(List.class)) {
Type genericReturnType = method.getGenericReturnType();
String typeName = genericReturnType.getTypeName();
String beanName = typeName.substring(typeName.indexOf("<") + 1,
typeName.indexOf(">"));
Class<?> aClass = Class.forName(beanName);
result = DataBindingUtils.autowireData(rs, aClass, DataType.LIST);
} else {
result = DataBindingUtils.autowireData(rs,
method.getReturnType(),
DataType.OBJECT);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return result;
}
}
4.最最最關鍵的參數封裝邏輯
public static PreparedStatement parseParams(Method method, Object[] args, Connection connection, String sql) throws SQLException {
// 解析出sql語句中所有的佔位符,按照順序放到一個數組中
Map<Integer, String> map = new HashMap<>();
sql = StringUtils.transformPlaceholders(sql, map);
PreparedStatement ps = connection.prepareStatement(sql);
// 這裏包含了所有的通過註解標註的參數名稱
String[] params = ParameterNameUtils.getMethodParameterNamesByAnnotation(method);
//判斷是否有參數
if (method.getParameterCount() != 0) {
// 現在的sql就是常規的直接是佔位符 ? 的sql語句了
// 現在開始解析參數
// String username,String password
// select * from password = #{password} and username = #{username}
// 這一步解決所有的參數都是通用類型的問題
int i = 0;
if (params != null && params.length > 0) {
for (; i < params.length; i++) {
if (DataBindingUtils.isCommonType(args[i].getClass())) {
Set<Integer> integers = map.keySet();
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()) {
Integer index = iterator.next();
String s = map.get(index);
if (params[i].equals(s.substring(2, s.length() - 1))) {
ps.setObject(index, args[i]);
iterator.remove();
break;
}
}
}
}
}
// 接下來解決當參數中存在bean類型的變量
if (args.length > params.length) {
for (; i < args.length; i++) {
if (DataBindingUtils.isBean(args[i].getClass())) {
// 然後對比map看看那個field的值和map中的值相同
Set<Integer> integers = map.keySet();
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()) {
Integer index = iterator.next();
String s = map.get(index);
// 直接拿到指定Field就行了吧???
try {
Field field = args[i].
getClass().
getDeclaredField(
s.substring(2, s.length() - 1));
field.setAccessible(true);
// 然後通過對象直接拿到這個Field的值
Object o = field.get(args[i]);
// 設置進rs
ps.setObject(index, o);
iterator.remove();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
return ps;
}
5.非查詢註解解析
public class NotQueryParse implements CrudParse {
@Override
public Object parse(Object peoxy,
Method method,
Object[] args,
Connection connection) {
Object result = null;
ResultSet rs = null;
try {
//先解析出NotQuery註解
NotQuery annotation = method.getAnnotation(NotQuery.class);
PreparedStatement ps = DataBindingUtils.parseParams(method,
args,
connection,
annotation.value());
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
}