手写实现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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图书管理系统是一个用于管理图书信息的系统,我们可以使用Spring、Spring MVCMyBatis实现这个系统。 首先,我们可以使用Spring来创建图书管理系统的核心应用程序。Spring提供了依赖注入、AOP、事务管理等功能,可以帮助我们更好地组织和管理系统的各个模块。通过Spring的配置文件,我们可以配置系统中的Bean并定义它们之间的关系,使系统能够更好地进行扩展和维护。 其次,我们可以使用Spring MVC实现系统的Web层。Spring MVC是一个基于MVC模式的Web框架,可以帮助我们更好地组织和管理用户请求与页面响应。通过Spring MVC,我们可以创建各种Controller来处理用户请求,并将用户的输入数据传递给后端的处理逻辑,最终将处理结果返回给用户。同时,Spring MVC还提供了各种特性,如数据绑定、表单验证、RESTful风格的URL等,能够帮助我们更好地构建用户友好的Web应用程序。 最后,我们可以使用MyBatis实现系统的数据持久层。MyBatis是一个持久层框架,可以帮助我们更好地管理数据库操作。通过MyBatis,我们可以使用XML或者注解的方式来定义数据库操作,同时也可以将数据库操作映射到Java对象,从而简化数据的处理和管理。使用MyBatis,我们可以更好地与数据库交互,提高系统的性能和可维护性。 综上所述,通过使用Spring、Spring MVCMyBatis,我们可以更好地实现图书管理系统。这些框架提供了丰富的功能和特性,能够帮助我们更好地组织和管理系统的各个层面,从而实现一个高性能、易扩展、易维护的图书管理系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值