手寫實現MVC+Mybatis框架,基本功能完全實現,並進行了一定的擴展。

一.手寫MVC框架

源碼位置:github網址

1.項目背景

因爲學校開始進行實訓,然後進行選題,大多數的都是商城了,管理系統了什麼的,這些項目大多數我都已經寫過了,實在是太無聊了,然後項目要求用原生的servlet進行實現。。。。emmmm實在是太麻煩了,寫一個請求就要創建一個servlet,所以我就想能不能自己實現一個類似於Dispatchservlet的servlet請求分發功能呢。因此該框架應運而生。

2.技術棧

Java基礎,Java反射,註解,servlet,JavaWeb,jsp。

3.功能實現

  1. 可以自動掃描所有的handler類。
  2. 可以進行請求分發。
  3. 可以進行自動實例化對象,就是控制反轉。
  4. 可以進行自動注入。
  5. 可以進行參數自動封裝。
  6. 可以進行Service層和Dao層的控制反轉。
  7. 可以通過註解改變bean的屬性和jsp以及sql的對應映射。
  8. 組件掃描
  9. 封裝參數後置處理器
  10. 封裝數據後置處理器
  11. json格式轉換。
  12. 視圖解析器
  13. 可以根據原生的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

  1. 功能:

    • 註解掃描。
    • 自動實例化
    • 自動注入
    • 請求映射
    • 請求分發,視圖處理
    • 參數綁定
  2. 代碼

    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

  1. 功能:

    • 參數類型的枚舉類
  2. 代碼

    package com.shy.myenum;
    
    /**
     * @author 石皓巖
     * @create 2020-02-28 15:51
     * 描述:數據類型
     */
    public enum DataType {
        /**
         * 封裝數據的時候。返回值是List集合
         */
        LIST,
        /**
         * 封裝數據的時候,返回值是單個對象
         */
        OBJECT,
        /**
         * 表類型
         */
        TABLE,
        /**
         * request中參數的類型,用來封裝數據的
         */
        REQUEST
    }
    
    

3.ClassUtils

  1. 功能:

    • Class類型掃描,返回class類。
  2. 代碼

    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

  1. 功能:

    • request參數綁定。支持駝峯命名,支持下劃線命名、模糊大小寫、支持註解微調。
    • mysql數據庫參數綁定。支持駝峯命名,支持下劃線命名、模糊大小寫、支持註解微調。例如:creatTime = creattime = creat_name
    • bean參數綁定支持,級聯綁定,比如說Student對象中包含Teacher對象,Teacher對象中包含Project對象。支持全部參數綁定。
  2. 代碼

    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

  1. 功能

    • 解析出方法中標註了@Param參數的value,然後其他的地方能進行自動參數注入。
  2. 代碼

    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層實現思路。

  1. 可以把所有的dao層定義接口形式的。
  2. 提供select、insert、update、delete註解進行數據庫的操作。
  3. 其實想一下,所有的數據庫執行步驟基本相同。
  4. 我們首先掃描出所有的DAO接口
  5. 然後通過動態代理的方式進行核心邏輯編寫。就是實現一個InvocationHandler這個接口然後編寫核心代碼。
  6. public Object invoke(Object proxy, Method method, Object[] args);方法是這樣的。
  7. 我們可以通過method拿到他的註解,然後我們定義幾個類用來分別處理select、insert、update、delete註解。
  8. 比如說select解析類,解析註解,解析出sql語句,我們還需要進行語法規定,比如說參數#{}可以獲取到參數。或者?,然後通過args參數進行封裝,因爲我們肯定把連接對象提前進行注入了,所以直接執行查詢語句,封裝參數就行了。

3.引發的問題

  1. 問:什麼時候,得到代理對象?

    解決方案:當我們框架進行掃描類的時候,發現是dao註解標註的類型,直接進行保存。然後實例化的時候,我們直接根據類型獲取代理對象就行了。

二.手寫mybatis框架

1.項目背景

由於我們已經晚上了上面的所有操作,但是發現數據庫層次還是存在大量的重複代碼,於是我們想借鑑一下mybatis的設計,完全註解或者配置文件的方式進行數據庫查詢,所有的Dao層全是接口,不需要實現類,只需要標註一下註解就好了。

2.技術棧

Java基礎,Java反射,註解,jdbc,動態代理。

3.功能實現

  1. dao層標註@Dao註解,然後dao層就可以完全是接口了,但是我做了個擴展,就是我們直接寫dao的實現類也行。
  2. 標註select註解自動查詢和封裝返回數據
  3. 標註insert註解自動插入數據
  4. 標註update註解,自動更新數據。
  5. 標註delete註解,自動刪除數據。
  6. 標註NotQuery註解,這個相當於增強型功能,所有的非查詢方法都可以用這個,會自動進行各種操作。
  7. 參數匹配,支持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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章