一.手写MVC框架
源码位置:github网址
1.项目背景
因为学校开始进行实训,然后进行选题,大多数的都是商城了,管理系统了什么的,这些项目大多数我都已经写过了,实在是太无聊了,然后项目要求用原生的servlet进行实现。。。。emmmm实在是太麻烦了,写一个请求就要创建一个servlet,所以我就想能不能自己实现一个类似于Dispatchservlet的servlet请求分发功能呢。因此该框架应运而生。
2.技术栈
Java基础,Java反射,注解,servlet,JavaWeb,jsp。
3.功能实现
- 可以自动扫描所有的handler类。
- 可以进行请求分发。
- 可以进行自动实例化对象,就是控制反转。
- 可以进行自动注入。
- 可以进行参数自动封装。
- 可以进行Service层和Dao层的控制反转。
- 可以通过注解改变bean的属性和jsp以及sql的对应映射。
- 组件扫描
- 封装参数后置处理器
- 封装数据后置处理器
- json格式转换。
- 视图解析器
- 可以根据原生的jdbc进行参数封装。
4.注解以及功能介绍
- AutoInstantiate:自动注入注解,当一个属性被标注了这个注解之后,表示这个属性会被自动注入实例对象。但是必须是框架所管理的对象。
- Bean:应用在类上,表示这是一个Bean类,当作为方法参数的时候,能够进行自动封装数据。当然可以不标注,标注效率会高一点。
- ColumnName:这个注解是标注在Bean属性上面的,表示这个字段要和数据库中的列名进行匹配。为了更好的参数封装。默认使用字段名。
- JspParameter:这个注解是标注在Bean属性上面的,表示这个字段要和JSP界面中传递的参数进行匹配,为了更好的参数封装。默认使用字段名。
- WebModule:这个注解是标注在类上的,表示这个类是控制类。会被框架所管理。
- Service:这个注解是标注在类上的,表示这是Service层,会被框架所管理。
- Dao:这个注解是标注在类上的,表示这是Dao层,会被框架所管理。
- Module:这个注解是标注在类上的,表示这是一个框架组件,会被框架所管理。
- Param:这个注解是标注在方法参数上的,表示这个参数要从前台数据拿值,必须标注,如果不标注的话不会进行自动参数封装。
- ParamPostProcesser:标注在类上,参数绑定后置处理器,当对Bean对象绑定参数的时候可能会出现类型不匹配,这个我们可以在这个后置处理器中进行类型匹配和注入。当然我们对这个后置处理器提供了一个默认方法,就是我们可以改变对象的值(只有这一次的机会)。
- DataPostProcessor:标注在类上,数据库参数绑定后置处理器,当我们后面需要扩展类型的比对的时候会调用这个接口, 比如说我们的属性有了一个新的类型,并且这个类型还对应着数据库中的类型*,我们就需要继承这个接口,并重写方法。还是提供了一次改变对象的机会。
- RequestMapping:就是handler映射。标注在方法上。
- ResponseBody:标注在方法上,表示这个方法不走视图处理器,直接返回JSON数据。
- RestWebModule:标注在类上,类似于RestController表示当前类所有的方法全不走视图处理器,直接返回JSON数据。
- ReturnPage:标注在类上,返回视图的前缀和后缀。默认是/WEB-INF/jsp/xxx.jsp 可以进行更改。标注在方法上,表示当前方法的返回视图的前缀和后缀。
5.代码实现
1.MainServlet
-
功能:
- 注解扫描。
- 自动实例化
- 自动注入
- 请求映射
- 请求分发,视图处理
- 参数绑定
-
代码
package com.shy.servlet; import cn.hutool.json.JSONUtil; import com.shy.annotation.*; import com.shy.utils.ClassUtils; import com.shy.utils.MyUtils; import com.shy.utils.ParameterNameUtils; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; /** * @author 石皓岩 * @create 2020-06-15 17:43 * 描述: */ @WebServlet("/") public class MainServlet extends HttpServlet { /** * 用来保存映射关系的 * /student/list -> method */ private static Map<String, Method> methodMap = new ConcurrentHashMap<>(); /** * 保存所有的标注了WebModule注解的类 */ private static List<Class> webModuleClasses = new CopyOnWriteArrayList<>(); /** * 保存了方法和实例化对象的缓存 * method -> 这个方法的类的对象 */ private static Map<Method, Object> methodObjectMap = new ConcurrentHashMap<>(); /** * 实例化池 * Class -> 实例化对象 */ public static Map<Class, Object> singleMap = new ConcurrentHashMap<>(); /** * 默认返回界面的前缀和后缀 */ private final static String PREFIX = "/jsp/"; private final static String SUFFIX = ".jsp"; /** * 请求转发 */ private final static String FORWARD = "forward"; /** * 请求重定向 */ private final static String REDIRECT = "redirect"; /** * 默认的错误界面 */ private final static String ERROR_PAGE = "/404.jsp"; /** * 这个用来保存request域中的参数 */ private final static Map<String, Object> REQUESTPARAM = new ConcurrentHashMap<>(); static { //1.扫描所有的包,并拿到该项目所有的类 List<Class> classes = scanAllModule(); //2.搞一个实例化缓存池,直接在下面的循环中实例化了。拿到所有的标注了WebModule的注解类 instantiationModule(classes); //3.拿到webModule类的所有方法 HandllerMapping(); //4.自动注入功能 autoInstantiation(); } private static void HandllerMapping() { for (Class webModuleClass : webModuleClasses) { Method[] methods = webModuleClass.getMethods(); //4.拿到方法上的所有注解RequestMapping的方法,并且放到一个map映射中 for (Method method : methods) { RequestMapping annotation = method.getAnnotation(RequestMapping.class); if (annotation != null) { //如果一个方法标注了RequestMapping注解,那就拿到它的值 String value = annotation.value(); //把当前方法和字符串进行映射 methodMap.put(value, method); try { methodObjectMap.put(method, singleMap.get(webModuleClass)); } catch (Exception e) { e.printStackTrace(); } } } } } private static void instantiationModule(List<Class> classes) { for (Class aClass : classes) { // 实例化所有的module if (aClass.isInterface()) { continue; } try { if (aClass.getAnnotation(WebModule.class) != null) { webModuleClasses.add(aClass); singleMap.put(aClass, aClass.newInstance()); } else if (aClass.getAnnotation(Service.class) != null) { singleMap.put(aClass, aClass.newInstance()); } else if (aClass.getAnnotation(Dao.class) != null) { singleMap.put(aClass, aClass.newInstance()); } else if (aClass.getAnnotation(Module.class) != null) { singleMap.put(aClass, aClass.newInstance()); } } catch (Exception ex) { ex.printStackTrace(); } } } /** * 自动实例化 */ private static void autoInstantiation() { Set<Class> classes1 = singleMap.keySet(); for (Class aClass : classes1) { // 拿到所有的属性 Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { declaredField.setAccessible(true); // 判断是否添加了自动注入 AutoInstantiate annotation = declaredField.getAnnotation(AutoInstantiate.class); if (annotation != null) { // 拿到这个属性的类型 Class<?> type = declaredField.getType(); try { // 如果是接口的话,就要进行类型注入 if (type.isInterface()) { for (Class aClass1 : classes1) { Class[] interfaces = aClass1.getInterfaces(); for (Class anInterface : interfaces) { if (anInterface.equals(type)) { type = aClass1; } } } } declaredField.set(singleMap.get(aClass), singleMap.get(type)); } catch (Exception e) { e.printStackTrace(); } } } } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置编码 setCharacterEncoding(request, response, "UTF-8"); //5.拿到请求的路径,并找出相应的方法。 /student Method method = getRequestMappingMethod(request, response); //6.通过注解解析出方法的所有参数 Object[] args = getArgsAndBindParam(request, response, method); //9.执行方法 Object object = invokeMethodAndBindMap(request, method, args); //10.执行视图解析 viewResolver(request, response, method, object); } private void viewResolver(HttpServletRequest request, HttpServletResponse response, Method method, Object object) throws IOException, ServletException { // 这一步是为了判断是否需要直接返回JSON串 if (!returnJson(response, method, object)) { // 如果没有的话 直接进行跳转界面 gotoPage(request, response, method, object); } } private boolean returnJson(HttpServletResponse response, Method method, Object object) throws IOException { ResponseBody annotation1 = method.getAnnotation(ResponseBody.class); Object o = methodObjectMap.get(method); RestWebModule annotation = o.getClass().getAnnotation(RestWebModule.class); if (annotation1 != null || annotation != null) { // 这里需要通过json工具进行转化 但是我没弄 String s = JSONUtil.toJsonStr(object); response.setCharacterEncoding("GBK"); response.getWriter().write(s); return true; } return false; } private void setCharacterEncoding(HttpServletRequest request, HttpServletResponse response, String character) throws UnsupportedEncodingException { request.setCharacterEncoding(character); response.setCharacterEncoding(character); } /** * 如果没加注解的话首先判断是否是请求转发和请求重定向 * * @param request * @param response * @param method 执行的方法 * @param object 执行方法的返回值 * @throws ServletException * @throws IOException */ private void gotoPage(HttpServletRequest request, HttpServletResponse response, Method method, Object object) throws ServletException, IOException { String invoke = String.valueOf(object); // 判断一下是否直接跳转界面 if (invoke.contains(":")) { String[] split = invoke.split(":"); if (split[0].equals(FORWARD)) { // 直接进行请求转发 request.getRequestDispatcher(split[1]).forward(request, response); } if (split[0].equals(REDIRECT)) { response.sendRedirect(split[1]); } } else { // 都没有的话直接进行默认跳转界面 String path = PREFIX + invoke + SUFFIX; // 通过返回值跳转界面,拼接字符串,进行跳转界面 //首先确定一下当前方法是否配置了前缀和后缀 Object o = methodObjectMap.get(method); ReturnPage annotation1 = o.getClass().getAnnotation(ReturnPage.class); if (annotation1 != null) { path = annotation1.prefix() + invoke + annotation1.suffix(); } ReturnPage annotation = method.getAnnotation(ReturnPage.class); if (annotation != null) { path = annotation.prefix() + invoke + annotation.suffix(); } // 返回界面 request.getRequestDispatcher(path).forward(request, response); } } private Object invokeMethodAndBindMap(HttpServletRequest request, Method method, Object[] args) { Object o = methodObjectMap.get(method); // 我可以拿到返回值 Object object = null; try { object = method.invoke(o, args); } catch (Exception e) { e.printStackTrace(); } //这一步是判断是否我们通过map添加了参数,如果添加了就放到request域中 if (REQUESTPARAM.size() > 0) { Set<String> objects = REQUESTPARAM.keySet(); Iterator<String> iterator = objects.iterator(); if (iterator.hasNext()) { String key = iterator.next(); request.setAttribute(key, REQUESTPARAM.get(key)); } } REQUESTPARAM.clear(); return object; } private Object[] getArgsAndBindParam(HttpServletRequest request, HttpServletResponse response, Method method) { // 当我们没有参数的时候直接返回就行了 if (method.getParameterCount() == 0) { return null; } // 这里会拿到所有的标注了@Param注解的参数 // 我们只要想通过参数拿简单值,就需要标注这个注解 String[] methodParameterNames = ParameterNameUtils.getMethodParameterNamesByAnnotation(method); String value = null; Object[] args = new Object[method.getParameterCount()]; int i = 0; // 设置标注了注解的参数 for (String methodParameterName : methodParameterNames) { args[i] = request.getParameter(methodParameterName); i++; } // 封装request和response还有map // 拿到所有的参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); int j = 0; for (Class<?> parameterType : parameterTypes) { // 注入基本类型的属性 if (MyUtils.isCommonType(parameterType)) { if (parameterType.equals(Integer.class) || parameterType.equals(int.class)) { args[j] = Integer.parseInt((String) args[j]); j++; } else if (parameterType.equals(Double.class) || parameterType.equals(double.class)) { args[j] = Double.parseDouble((String) args[j]); j++; } else if (parameterType.equals(Long.class) || parameterType.equals(long.class)) { args[j] = Long.parseLong((String) args[j]); j++; } } else if (parameterType.equals(HttpServletRequest.class)) { //注入request args[i] = request; i++; } else if (parameterType.equals(HttpServletResponse.class)) { //注入response args[i] = response; i++; } else if (parameterType.equals(HttpSession.class)) { //注入session args[i] = request.getSession(); i++; } else if (parameterType.equals(Map.class)) { //注入map args[i] = REQUESTPARAM; i++; } else if (MyUtils.isBean(parameterType)) { //如果是bean类型的话,自动注入 args[i] = MyUtils.autowriteParameter(request, parameterType); i++; } } return args; } private Method getRequestMappingMethod(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String servletPath = request.getServletPath(); Method method = methodMap.get(servletPath); if (method == null) { request.getRequestDispatcher(ERROR_PAGE).forward(request, response); } return method; } /** * 根据当前servlet的路径扫描所有的servlet * * @param mainServletClass * @return */ private static List<Class> scanWebModule(Class<?> mainServletClass) { String packageName = mainServletClass.getName().substring(0, mainServletClass.getName().lastIndexOf(".")); Set<String> className = ClassUtils.getClassName(packageName, true); List<Class> classList = new ArrayList<>(); for (String s : className) { if (packageName.equals(s)) { continue; } Class<?> aClass = null; try { aClass = Class.forName(s); } catch (ClassNotFoundException e) { e.printStackTrace(); } classList.add(aClass); } return classList; } private static List<Class> scanAllModule() { Set<String> className = ClassUtils.getClassName("", true); List<Class> classList = new ArrayList<>(); for (String s : className) { Class<?> aClass = null; try { aClass = Class.forName(s.substring(1)); } catch (ClassNotFoundException e) { e.printStackTrace(); } classList.add(aClass); } return classList; } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
2.DataType
-
功能:
- 参数类型的枚举类
-
代码
package com.shy.myenum; /** * @author 石皓岩 * @create 2020-02-28 15:51 * 描述:数据类型 */ public enum DataType { /** * 封装数据的时候。返回值是List集合 */ LIST, /** * 封装数据的时候,返回值是单个对象 */ OBJECT, /** * 表类型 */ TABLE, /** * request中参数的类型,用来封装数据的 */ REQUEST }
3.ClassUtils
-
功能:
- Class类型扫描,返回class类。
-
代码
package com.shy.utils; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * @author 石皓岩 * @create 2020-06-15 21:58 * 描述: */ public class ClassUtils { public static void main(String[] args) throws Exception { String packageName = ""; Set<String> classNames = getClassName(packageName, true); if (classNames != null) { for (String className : classNames) { System.out.println(className); } } } /** * 获取某包下所有类 * @param packageName 包名 * @param isRecursion 是否遍历子包 * @return 类的完整名称 */ public static Set<String> getClassName(String packageName, boolean isRecursion) { Set<String> classNames = null; ClassLoader loader = Thread.currentThread().getContextClassLoader(); String packagePath = packageName.replace(".", "/"); URL url = loader.getResource(packagePath); if (url != null) { String protocol = url.getProtocol(); if (protocol.equals("file")) { classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion); } else if (protocol.equals("jar")) { JarFile jarFile = null; try{ jarFile = ((JarURLConnection) url.openConnection()).getJarFile(); } catch(Exception e){ e.printStackTrace(); } if(jarFile != null){ getClassNameFromJar(jarFile.entries(), packageName, isRecursion); } } } else { /*从所有的jar包中查找包名*/ classNames = getClassNameFromJars(((URLClassLoader)loader).getURLs(), packageName, isRecursion); } return classNames; } /** * 从项目文件获取某包下所有类 * @param filePath 文件路径 * @param className 类名集合 * @param isRecursion 是否遍历子包 * @return 类的完整名称 */ private static Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) { Set<String> className = new HashSet<String>(); File file = new File(filePath); File[] files = file.listFiles(); for (File childFile : files) { if (childFile.isDirectory()) { if (isRecursion) { className.addAll(getClassNameFromDir(childFile.getPath(), packageName+"."+childFile.getName(), isRecursion)); } } else { String fileName = childFile.getName(); if (fileName.endsWith(".class") && !fileName.contains("$")) { className.add(packageName+ "." + fileName.replace(".class", "")); } } } return className; } /** * @param jarEntries * @param packageName * @param isRecursion * @return */ private static Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion){ Set<String> classNames = new HashSet<String>(); while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); if(!jarEntry.isDirectory()){ /* * 这里是为了方便,先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug * (FIXME: 先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug) */ String entryName = jarEntry.getName().replace("/", "."); if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) { entryName = entryName.replace(".class", ""); if(isRecursion){ classNames.add(entryName); } else if(!entryName.replace(packageName+".", "").contains(".")){ classNames.add(entryName); } } } } return classNames; } /** * 从所有jar中搜索该包,并获取该包下所有类 * @param urls URL集合 * @param packageName 包路径 * @param isRecursion 是否遍历子包 * @return 类的完整名称 */ private static Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) { Set<String> classNames = new HashSet<String>(); for (int i = 0; i < urls.length; i++) { String classPath = urls[i].getPath(); //不必搜索classes文件夹 if (classPath.endsWith("classes/")) {continue;} JarFile jarFile = null; try { jarFile = new JarFile(classPath.substring(classPath.indexOf("/"))); } catch (IOException e) { e.printStackTrace(); } if (jarFile != null) { classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion)); } } return classNames; } }
4.DataBindingUtils
-
功能:
- request参数绑定。支持驼峰命名,支持下划线命名、模糊大小写、支持注解微调。
- mysql数据库参数绑定。支持驼峰命名,支持下划线命名、模糊大小写、支持注解微调。例如:creatTime = creattime = creat_name
- bean参数绑定支持,级联绑定,比如说Student对象中包含Teacher对象,Teacher对象中包含Project对象。支持全部参数绑定。
-
代码
package com.shy.utils; import cn.hutool.core.bean.BeanUtil; import com.shy.annotation.*; import com.shy.myenum.DataType; import com.shy.servlet.MainServlet; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author 石皓岩 * @create 2020-02-28 18:38 * 描述:该工具类是我写的一个完成自动装配的功能包 */ @SuppressWarnings("all") public class DataBindingUtils { //所有的属性保存在这里 private static Map<String, Field> allFields = new ConcurrentHashMap<>(); //因为最后需要进行一些字符串比对,会经过一些转换 //这个里面维护了原始字符串 关系:转换后的->原始字符串 private static Map<String, String> alias = new ConcurrentHashMap<>(); // 这个是维护bean类型的映射表 private static Map<String, Map<String, Field>> beanMap = new ConcurrentHashMap<>(); private static void cuttingParameters(Map<String, Object> map, String s1) { String[] split1 = s1.split("="); if (split1.length == 2) { String name = split1[0]; String value = split1[1]; //进行字符串处理 name = getNotUnderlineString(name); map.put(name, value); } else if (split1.length == 1) { String name = split1[0]; map.put(name, ""); } } /** * 自动封装参数,我们可以自己调用,但是也可以不用,因为我们自己进行调用了,当我们调用的方法有参数的时候, * 会调用这里进行自动封装数据 * * @param request * @param dataType * @param <T> * @return */ public static <T> T autowriteParameter(HttpServletRequest request, Class dataType) { if (request == null) { throw new RuntimeException("request不能为空"); } if (dataType == null) { throw new RuntimeException("class类型不能为空"); } try { // 得到对象并且会把Field进行映射 Object o = getObjectAndIntegrateMap(dataType, DataType.REQUEST); // 设置常见类型 setCommonParam(o, request); // 设置bean类型的参数 setBeanParam(o, request); return (T) o; } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * 设置通用参数,private 不允许其他人调用,用来从reuqest中获取参数并设置 * * @param o * @param request */ private static void setCommonParam(Object o, HttpServletRequest request) { Set<String> keySet1 = allFields.keySet(); Iterator<String> iterator = keySet1.iterator(); while (iterator.hasNext()) { String key = iterator.next(); Field field = allFields.get(key); String value = request.getParameter(key); if (isCommonType(field.getType())) { setCommonType(o, value, field); iterator.remove(); } } } /** * 设置bean参数,private 不允许其他人调用,用来从reuqest中获取参数并设置 * * @param o * @param request */ private static void setBeanParam(Object o, HttpServletRequest request) throws InstantiationException, IllegalAccessException { Set<String> keySet = allFields.keySet(); Iterator<String> iterator = keySet.iterator(); while (iterator.hasNext()) { String name = iterator.next(); Field field = allFields.get(name); Object o1 = field.getType().newInstance(); if (beanMap.containsKey(name)) { Map<String, Field> stringFieldMap = beanMap.get(name); Set<String> keySet1 = stringFieldMap.keySet(); Iterator<String> iterator1 = keySet1.iterator(); while (iterator1.hasNext()) { String next = iterator1.next(); String parameter = request.getParameter(next); if (parameter == null) { continue; } if (stringFieldMap.containsKey(next)) { Field child = stringFieldMap.get(next); setCommonType(o1, parameter, child); iterator1.remove(); } } field.set(o, o1); iterator.remove(); beanMap.remove(name); } } } private static Object getObjectAndIntegrateMap(Class dataType, DataType tableOrJsp) throws InstantiationException, IllegalAccessException { allFields.clear(); alias.clear(); //拿到这个对象的所有属性 Object o = dataType.newInstance(); Field[] all = getAllFields(dataType); //把所有的属性整合到一起 //里面涉及到了转换字符串的算法,就是把所有的字符串转换成小写 //如果有_就删除掉 doTransFormMap(all, tableOrJsp); return o; } private static Field[] getAllFields(Class dataType) { Field[] declaredFields = dataType.getDeclaredFields(); Field[] declaredFields1 = dataType.getSuperclass().getDeclaredFields(); Field[] all = new Field[declaredFields.length + declaredFields1.length]; System.arraycopy(declaredFields, 0, all, 0, declaredFields.length); System.arraycopy(declaredFields1, 0, all, declaredFields.length, declaredFields1.length); return all; } /** * 我自己写的封装数据的方法他会根据你传递进来的rs进行遍历寻找出查询出来的数据, * class是将要封装成的对象 * dataType是将要返回值的类型 支持List和Object * 最后一个参数是后置处理器 * * @param rs * @param * @param * @return */ public static <T> T autowireData(ResultSet rs, Class clazz, DataType dataType) { if (rs == null) { throw new RuntimeException("ResultSet不能为空"); } if (clazz == null) { throw new RuntimeException("class类型不能为空"); } //用来保存数据的 List data = new ArrayList<>(); //遍历数据 try { while (rs.next()) { //创建实例 Object o = DataBindingUtils.getObjectAndIntegrateMap(clazz, DataType.TABLE); //拿到所有的rs中的列明 //进行自动装配 doAssembly(rs, o); //装入list data.add(o); } } catch (Exception e) { throw new RuntimeException("数据封装失败,请检查将要封装的类型和查询出来的类型是否一致"); } if (dataType == null || dataType.equals(DataType.OBJECT)) { if (data.size() == 0) { return null; } return (T) data.get(0); } else if (dataType.equals(DataType.LIST)) { if (data.size() == 0) { return null; } return (T) data; } return null; } private static void doAssembly(ResultSet rs, Object o) throws SQLException, IllegalAccessException { ResultSetMetaData metaData = rs.getMetaData(); int i = 1; int temp; for (; i <= metaData.getColumnCount(); i++) { //这一步拿到原值,并且原值和转换后的值做了映射放到了alias这个别名集合中 String columnName = getSimpleColumnName(metaData.getColumnName(i)); // 直接封装普通对象 if (allFields.containsKey(columnName)) { Field field = allFields.get(columnName); setCommonType(o, String.valueOf(rs.getObject(alias.get(columnName))), field); //我给你一次对每一列重新匹配的机会,并且给你重新改变对象的机会 invokePostProcesser(rs, o, columnName, field); allFields.remove(columnName); alias.remove(columnName); } else { try { temp = i; Iterator<String> iterator1 = beanMap.keySet().iterator(); while (iterator1.hasNext()) { i = temp; Object child = null; String beanName = iterator1.next(); if (allFields.containsKey(beanName)) { // 这个是teacher的filed Field field = allFields.get(beanName); if (child == null) { child = field.getType().newInstance(); } Map<String, Field> stringFieldMap = beanMap.get(beanName); for (; i <= metaData.getColumnCount(); i++) { columnName = getSimpleColumnName(metaData.getColumnName(i)); if (stringFieldMap.containsKey(columnName)) { Field childField = stringFieldMap.get(columnName); if (rs.getObject(columnName) == null) { continue; } setCommonType(child, String.valueOf(rs.getObject(alias.get(columnName))), childField); stringFieldMap.remove(columnName); alias.remove(columnName); } else { beanMap.remove(beanName); allFields.remove(beanName); alias.remove(columnName); } } field.set(o, child); } } } catch (Exception ex) { ex.printStackTrace(); } } } } private static String getSimpleColumnName(String name) { String columnName = name.toLowerCase(); //进行字符串处理,就是得到去掉下划线的列名 columnName = getNotUnderlineString(columnName); alias.put(columnName, name); return columnName; } private static void setCommonType(Object o, String value, Field field) { try { if (value == null) { return; } else if (field.getType().equals(String.class)) { field.set(o, value); } else if (field.getType().equals(int.class) || field.getType().equals(Integer.class)) { field.set(o, Integer.parseInt(value)); } else if (field.getType().equals(double.class) || field.getType().equals(Double.class)) { field.set(o, Double.parseDouble(value)); } else if (field.getType().equals(float.class) || field.getType().equals(Float.class)) { field.set(o, Float.parseFloat(value)); } else if (field.getType().equals(Long.class) || field.getType().equals(long.class)) { field.set(o, Long.parseLong(value)); } } catch (Exception ex) { ex.printStackTrace(); } } private static void invokePostProcesser(ResultSet rs, Object o, String columnName, Field field) { Set<Class> classes = MainServlet.singleMap.keySet(); for (Class aClass : classes) { if (aClass.getInterfaces().length > 0 && aClass.getInterfaces()[0].equals(DataPostProcessor.class)) { DataPostProcessor dataPostProcessor = (DataPostProcessor) MainServlet.singleMap.get(aClass); dataPostProcessor.typeOfContrast(rs, field, o, columnName); dataPostProcessor.changeObject(o); } } } private static void doTransFormMap(Field[] all, DataType dataType) { for (Field field : all) { field.setAccessible(true); // 我想当解析每个类型的时候,手首先进行一个类型判断 if (!isCommonType(field.getType())) { String name = getCommonFieldName(field, dataType); Map<String, Field> map = new ConcurrentHashMap<>(); Field[] all1 = getAllFields(field.getType()); doTransFormMap(all1, dataType); for (Field field1 : all1) { field1.setAccessible(true); String childName = getCommonFieldName(field1, dataType); map.put(childName, field1); allFields.put(name, field); } beanMap.put(name, map); } else { String name = getCommonFieldName(field, dataType); //这个是为了防止父类的属性覆盖 if (!allFields.containsKey(name)) { allFields.put(name, field); } } } } private static String getCommonFieldName(Field field, DataType dataType) { String name = field.getName().toLowerCase(); //接下来需要处理注解,如果定义了注解就需要进行解析注解 if (dataType == null || dataType.equals(DataType.TABLE)) { ColumnName annotation = field.getAnnotation(ColumnName.class); if (annotation != null && !annotation.value().equals("")) { name = annotation.value().toLowerCase(); } } else if (dataType.equals(DataType.REQUEST)) { JspParameter annotation = field.getAnnotation(JspParameter.class); if (annotation != null && !annotation.value().equals("")) { name = annotation.value().toLowerCase(); } } //转换为没有下划线的字符串 name = getNotUnderlineString(name); return name; } /** * 抽取出来的方法,得到没有下划线的字符串 * * @param name * @return */ public static String getNotUnderlineString(String name) { //首先验证参数,name不能为空否则抛出 异常 if (name == null) { throw new RuntimeException("当转换字符串的时候,name不能为空"); } if (name.contains("_")) { String[] s = name.split("_"); name = ""; for (String s1 : s) { name += s1; } } return name; } public static boolean isBean(Class<?> parameterType) { /*//如果是基本类型直接返回 if (isCommonType(parameterType)) { return false; } // 怎么判断一个Class是bean类型呢? // 我觉得应该是我们对bean的定义吧,首先我觉得应该是对注解的判断,如果添加了bean注解没什么好说的,直接就是了 if (parameterType.getAnnotation(Bean.class) != null) { return true; } boolean flag = false; // 2.我觉得应该拿到所有的属性,判断是否所有的属性都私有 Field[] allFields = new Field[parameterType.getDeclaredFields().length + parameterType.getSuperclass().getDeclaredFields().length]; Field[] fields = parameterType.getDeclaredFields(); Field[] superFields = parameterType.getSuperclass().getDeclaredFields(); System.arraycopy(fields, 0, allFields, 0, fields.length); System.arraycopy(superFields, 0, allFields, fields.length, superFields.length); return isHasGetterAndSetter(parameterType, allFields);*/ return BeanUtil.isBean(parameterType); } public static boolean isCommonType(Class<?> parameterType) { if (parameterType.equals(String.class)) { return true; } else if (parameterType.equals(int.class) || parameterType.equals(Integer.class)) { return true; } else if (parameterType.equals(double.class) || parameterType.equals(Double.class)) { return true; } else if (parameterType.equals(float.class) || parameterType.equals(Float.class)) { return true; } else if (parameterType.equals(long.class) || parameterType.equals(Long.class)) { return true; } else if (parameterType.equals(short.class) || parameterType.equals(Short.class)) { return true; } else if (parameterType.equals(byte.class) || parameterType.equals(Byte.class)) { return true; } else if (parameterType.equals(char.class) || parameterType.equals(Character.class)) { return true; } else if (parameterType.equals(boolean.class) || parameterType.equals(Boolean.class)) { return true; } return false; } public static boolean isHasGetterAndSetter(Class<?> parameterType, Field[] fields) { boolean flag = false; for (Field field : fields) { if (field.isAccessible()) { break; } // 判断是否存在getter/setter方法 String name = field.getName(); name = name.substring(0, 1).toUpperCase() + name.substring(1); String getter = "get" + name; String setter = "set" + name; try { if (parameterType.getMethod(getter) == null) { flag = false; break; } if (parameterType.getMethod(setter, field.getType()) == null) { flag = false; break; } } catch (NoSuchMethodException e) { e.printStackTrace(); } flag = true; } return flag; } }
5.ParameterNameUtils
-
功能
- 解析出方法中标注了@Param参数的value,然后其他的地方能进行自动参数注入。
-
代码
package com.shy.utils; import com.shy.annotation.Param; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** * @author 石皓岩 * @create 2020-06-15 18:57 * 描述: */ public class ParameterNameUtils { /** * 获取指定方法的参数名 * * @param method 要获取参数名的方法 * @return 按参数顺序排列的参数名列表 */ public static String[] getMethodParameterNamesByAnnotation(Method method) { Annotation[][] parameterAnnotations = method.getParameterAnnotations(); if (parameterAnnotations == null || parameterAnnotations.length == 0) { return null; } int num = 0; for (Annotation[] parameterAnnotation : parameterAnnotations) { for (Annotation annotation : parameterAnnotation) { Class<? extends Annotation> aClass = annotation.annotationType(); if (aClass == Param.class) { num++; } } } String[] parameterNames = new String[num]; int i = 0; for (Annotation[] parameterAnnotation : parameterAnnotations) { for (Annotation annotation : parameterAnnotation) { if (annotation instanceof Param) { Param param = (Param) annotation; parameterNames[i++] = param.value(); } } } return parameterNames; } }
6.后续功能展望以及实现思路
1.功能展望:
- dao层的处理,就是数据库框架。
- 希望能支持配置文件的方式。
2.实现思路
dao层实现思路。
- 可以把所有的dao层定义接口形式的。
- 提供select、insert、update、delete注解进行数据库的操作。
- 其实想一下,所有的数据库执行步骤基本相同。
- 我们首先扫描出所有的DAO接口
- 然后通过动态代理的方式进行核心逻辑编写。就是实现一个InvocationHandler这个接口然后编写核心代码。
- public Object invoke(Object proxy, Method method, Object[] args);方法是这样的。
- 我们可以通过method拿到他的注解,然后我们定义几个类用来分别处理select、insert、update、delete注解。
- 比如说select解析类,解析注解,解析出sql语句,我们还需要进行语法规定,比如说参数#{}可以获取到参数。或者?,然后通过args参数进行封装,因为我们肯定把连接对象提前进行注入了,所以直接执行查询语句,封装参数就行了。
3.引发的问题
-
问:什么时候,得到代理对象?
解决方案:当我们框架进行扫描类的时候,发现是dao注解标注的类型,直接进行保存。然后实例化的时候,我们直接根据类型获取代理对象就行了。
二.手写mybatis框架
1.项目背景
由于我们已经晚上了上面的所有操作,但是发现数据库层次还是存在大量的重复代码,于是我们想借鉴一下mybatis的设计,完全注解或者配置文件的方式进行数据库查询,所有的Dao层全是接口,不需要实现类,只需要标注一下注解就好了。
2.技术栈
Java基础,Java反射,注解,jdbc,动态代理。
3.功能实现
- dao层标注@Dao注解,然后dao层就可以完全是接口了,但是我做了个扩展,就是我们直接写dao的实现类也行。
- 标注select注解自动查询和封装返回数据
- 标注insert注解自动插入数据
- 标注update注解,自动更新数据。
- 标注delete注解,自动删除数据。
- 标注NotQuery注解,这个相当于增强型功能,所有的非查询方法都可以用这个,会自动进行各种操作。
- 参数匹配,支持bean类型的匹配。
4.注解以及功能介绍
- Dao:上面已经介绍过了,就是dao层的注解。
- Delete:删除语句注解。
- Select:查询语句注解
- Insert:插入语句注解
- Update:更新语句注解
- NotQuery注解,非查询注解。
5.代码实现
1.获得代理对象
public class DaoProxy {
public static Object getProxy(Class interfaceClass, Connection connection){
// 直接代理对象
return Proxy.newProxyInstance(DaoProxy.class.getClassLoader(),
new Class[]{interfaceClass},
new RemoteInvocationHandler(interfaceClass,
connection));
}
}
2.代理核心逻辑
package com.shy.proxy;
import com.shy.annotation.*;
import com.shy.myinterface.CrudParse;
import com.shy.myinterface.impl.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
/**
* @author 石皓岩
* @create 2020-06-08 12:04
* 描述:
*/
public class RemoteInvocationHandler implements InvocationHandler {
private Class interfaceClass;
private Connection connection;
public RemoteInvocationHandler() {
}
public RemoteInvocationHandler(Class interfaceClass, Connection connection) {
this.interfaceClass = interfaceClass;
this.connection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
Object result = null;
if (method.getAnnotation(Select.class) != null) {
// 查询
result = parse(new SelectParse(), proxy, method, args, connection);
} else if (method.getAnnotation(Insert.class) != null) {
// 插入
parse(new InsertParse(), proxy, method, args, connection);
} else if (method.getAnnotation(Update.class) != null) {
// 更新
parse(new UpdateParse(), proxy, method, args, connection);
} else if (method.getAnnotation(Delete.class) != null) {
// 更新
parse(new DeleteParse(), proxy, method, args, connection);
} else if (method.getAnnotation(NotQuery.class) != null) {
// 更新
parse(new NotQueryParse(), proxy, method, args, connection);
}
return result;
}
public Object parse(CrudParse crudParse, Object proxy, Method method, Object[] args, Connection connection) {
return crudParse.parse(proxy, method, args, connection);
}
}
3.查询注解解析核心逻辑
package com.shy.myinterface.impl;
import com.shy.annotation.ReturnDataType;
import com.shy.annotation.Select;
import com.shy.myenum.DataType;
import com.shy.myinterface.CrudParse;
import com.shy.utils.DataBindingUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* @author 石皓岩
* @create 2020-06-22 15:12
* 描述:
*/
public class SelectParse implements CrudParse {
@Override
public Object parse(Object peoxy, Method method, Object[] args, Connection connection) {
Object result = null;
ResultSet rs = null;
try {
//先解析出select注解
Select annotation = method.getAnnotation(Select.class);
// 核心参数解析封装逻辑
PreparedStatement ps = DataBindingUtils.parseParams(method, args, connection, annotation.value());
rs = ps.executeQuery();
// 封装数据
result = getResult(method, result, rs, annotation);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/*
这里是最终的封装数据。。。。emmm做了优化,
你可以不必标注任何注解,所有的封装了返回值处理了,
我全部给你做了
*/
private Object getResult(Method method,
Object result,
ResultSet rs,
Select annotation) {
if (annotation.resultDataType() != null &&
!annotation.resultDataType().equals(Object.class)) {
result = DataBindingUtils.autowireData(rs,
annotation.resultDataType(),
annotation.dataType());
} else if (method.getAnnotation(ReturnDataType.class) != null) {
result = DataBindingUtils.autowireData(rs, method.getAnnotation(ReturnDataType.class).value(),
annotation.dataType());
} else {
try {
if (method.getReturnType().equals(List.class)) {
Type genericReturnType = method.getGenericReturnType();
String typeName = genericReturnType.getTypeName();
String beanName = typeName.substring(typeName.indexOf("<") + 1,
typeName.indexOf(">"));
Class<?> aClass = Class.forName(beanName);
result = DataBindingUtils.autowireData(rs, aClass, DataType.LIST);
} else {
result = DataBindingUtils.autowireData(rs,
method.getReturnType(),
DataType.OBJECT);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return result;
}
}
4.最最最关键的参数封装逻辑
public static PreparedStatement parseParams(Method method, Object[] args, Connection connection, String sql) throws SQLException {
// 解析出sql语句中所有的占位符,按照顺序放到一个数组中
Map<Integer, String> map = new HashMap<>();
sql = StringUtils.transformPlaceholders(sql, map);
PreparedStatement ps = connection.prepareStatement(sql);
// 这里包含了所有的通过注解标注的参数名称
String[] params = ParameterNameUtils.getMethodParameterNamesByAnnotation(method);
//判断是否有参数
if (method.getParameterCount() != 0) {
// 现在的sql就是常规的直接是占位符 ? 的sql语句了
// 现在开始解析参数
// String username,String password
// select * from password = #{password} and username = #{username}
// 这一步解决所有的参数都是通用类型的问题
int i = 0;
if (params != null && params.length > 0) {
for (; i < params.length; i++) {
if (DataBindingUtils.isCommonType(args[i].getClass())) {
Set<Integer> integers = map.keySet();
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()) {
Integer index = iterator.next();
String s = map.get(index);
if (params[i].equals(s.substring(2, s.length() - 1))) {
ps.setObject(index, args[i]);
iterator.remove();
break;
}
}
}
}
}
// 接下来解决当参数中存在bean类型的变量
if (args.length > params.length) {
for (; i < args.length; i++) {
if (DataBindingUtils.isBean(args[i].getClass())) {
// 然后对比map看看那个field的值和map中的值相同
Set<Integer> integers = map.keySet();
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()) {
Integer index = iterator.next();
String s = map.get(index);
// 直接拿到指定Field就行了吧???
try {
Field field = args[i].
getClass().
getDeclaredField(
s.substring(2, s.length() - 1));
field.setAccessible(true);
// 然后通过对象直接拿到这个Field的值
Object o = field.get(args[i]);
// 设置进rs
ps.setObject(index, o);
iterator.remove();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
return ps;
}
5.非查询注解解析
public class NotQueryParse implements CrudParse {
@Override
public Object parse(Object peoxy,
Method method,
Object[] args,
Connection connection) {
Object result = null;
ResultSet rs = null;
try {
//先解析出NotQuery注解
NotQuery annotation = method.getAnnotation(NotQuery.class);
PreparedStatement ps = DataBindingUtils.parseParams(method,
args,
connection,
annotation.value());
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
}