手搓SSM

Posted pengdt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手搓SSM相关的知识,希望对你有一定的参考价值。

相关资料,网上的资料很多,但是文章看不懂,看别人写好的代码比较好理解
ssm-example
mysssm

整个流程和原理

  • 一个入口类,入口类需要在tomcat启动的时候执行
  • 通过扫描文件加把文件取出来,存进classList里
  • 把mapper的注解扫描,创建一个叫mapper的map
  • 收集注解,存进各自的Map里【controllerMap,serviceMap,mapperMap】
  • 生成mybatis代理函数
  • 把serviceMap里的值赋值到controller层的Autowired上
  • 把mapperMap里的值赋值到service层的Autowired上
  • 把RequestMapping拼接成key,value是对应的类方法,存进handlerMap里
  • 重写HttpServlet的doGet和doPost方法

前提,下载maven依赖

  • servlet,tomocat依赖
  • dom4j,转化xml文件
  • mysql,连接数据库插件
  • lang3,工具包
  • fastjson,json工具

文件目录

技术图片

创建入口文件

// core/springmvc/DispatcherServlet.Java

public class DispatcherServlet extends HttpServlet {
    //收集文件路径的List
    List<String> classList = new ArrayList();
    Map<String, Object> controllerMap = new HashMap<String, Object>();
    Map<String, Object> serviceMap = new HashMap<String, Object>();
    Map<String, Object> mapperMap = new HashMap<String, Object>();
    // key是拼接的全路径,value是方法
    Map<String, Object> handlerMap = new HashMap<String, Object>();

    // 核心的入口方法
    // 上面的流程都在这个方法里
    @Override
    public void init() throws ServletException {
        // 扫描文件
        scanPackage("com.pdt.ssm");
        // 收集注解,存进对应的map里
        doInstance();
        // 生成proxy函数
        mybatisDo();
        // 把controller层的Autowired赋值上serviceMap的对应类
        controllerIoc();
        // 把service层的Autowired赋值上mapperMap的对应proxy函数
        serviceIoc();
        // 把RequestMapping拼接好作为key存进map里
        buildUrlMapping();
    }
}

web设置

// webapp/web-inf/web.xml
<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.pdt.core.springmvc.DispatcherServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

创建注解

  • springMvc注解【RequestMapping,RequestParam】
  • spring注解【Controller,Service,Autowired】
  • mybatis注解【Mapper,Select】

难点是Mybatis
使用过Mybatis可以知道,不过是xml方式还是注解方式,那个接口的方法并不存在,所以需要自己创建相对应的方法,需要两个用于储存的Bean

  • Function,用于存Select注解的数据
  • MapperBean,用于存Mapeer注解接口里的所有方法
private void mybatisDo() {
    // mapperMap是在上一个方法里搜集到的mapper注解的类
    for (Map.Entry<String, Object> entry : mapperMap.entrySet()) {
        Class clazz = (Class) entry.getValue();
        MapperBean mapper = new MapperBean();
        // 用来存储方法的list
        List<Function> list = new ArrayList<Function>();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            int annsLen = method.getAnnotations().length;
            if(annsLen==0){
                System.out.println("没有mybatis注解");
            }else{
                // 用来存储一条方法的记录
                Function function = new Function();
                // 如果是select注解
                if (method.isAnnotationPresent(Select.class)) {
                    function.setSqltype("select");
                    function.setSql(method.getAnnotation(Select.class).value());
                    function.setFuncName(method.getName());
                    function.setResultType(method.getReturnType().getName());
                    // 这个是获取泛型,为了自动注入
                    if(method.getGenericReturnType().getTypeName().contains("<")){
                        function.setSetGenericReturnType(method.getGenericReturnType().getTypeName().split("<")[1].split(">")[0]);
                    }
                }else{
                    // 其他注解
                }
                list.add(function);
                mapper.setInterfaceName(entry.getKey());
                mapper.setList(list);
            }
        }
        // 核心内容,通过Proxy创建一个新的方法
        Object obj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] {clazz},
                new MapperProxy(mapper));
        // 把创建出来的方法替代原本的key的内容
        entry.setValue(obj);
    }
}

核心中的核心,MapperProxy类

public class MapperProxy<T> implements InvocationHandler{
     private MapperBean mapperBean;
     public MapperProxy(MapperBean mapperBean) {
    this.mapperBean = mapperBean;
     }

     @Override
     public T invoke(Object proxy, Method method, Object[] args) throws Exception{
    List<Function> list = mapperBean.getList();
    if(null != list || 0 != list.size()){
        for(Function function : list) {
        // 看id是否和接口的方法名一样
        // 应该判断参数数量和格式是不是对应的
        if(method.getName().equals(function.getFuncName())){
           String ann = function.getSqltype();
           // 如果是select注解
               if(ann.equals("select")){
              // 判断返回类型,决定执行什么sql方法,具体查看代码
           }
      }
      return null;
     }
}

给service的Autowired注解赋值,controller同理

private void serviceIoc() {
    for (Map.Entry<String, Object> entry : serviceMap.entrySet()) {
        Object instance = entry.getValue();
        Class<?> clazz = instance.getClass();
        if (clazz.isAnnotationPresent(Service.class)) {
            //获得所有声明的参数 得到参数数组
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String key = autowired.value();
                    //打开私有属性的权限修改
                    field.setAccessible(true);
                    try {
                        //给变量重新设值,这个mapperMap取出的就是上面的Proxy生成的方法
                        field.set(instance, mapperMap.get(key));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                } else {
                    continue;
                }
            }
        } else {
            continue;
        }
    }
}

自动注入,这里实现了两处

  • 前端请求自动注入,查看下面的代码
  • 查询数据自动注入,同理
public static Object handleRequest(String typeStr, HttpServletRequest req) throws Exception{
    // typeStr是全路径的bean位置字符串,例如com.pdt.bean.User
    Object res = Class.forName(typeStr).newInstance();
    Field[] fields = Class.forName(typeStr).getDeclaredFields();
    for (Field field : fields) {
        String fieldType = field.getType().getName();
        String fieldName = field.getName();
        //打开私有属性的权限修改
        field.setAccessible(true);
        field.set(res,req.getParameter(fieldName));
    }
    // 这个返回的值就是已经存好了值的
    return res;
}

重写响应方法

// DispatcherServlet.Java

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //获取请求路径
    String url = req.getRequestURI();
    String context = req.getContextPath();
    String path = url.replace(context, "");
    Method method= (Method) handlerMap.get(path);
    // 从controllerMap取出对应key的类方法
    Object controller= null;
    //根据key去拿实例
    if(path.equals("/")){
        controller= controllerMap.get("/");
    }else{
        controller= controllerMap.get("/"+path.split("/")[1]);
    }
    try {
        if(controller==null || method==null){
            resp.setContentType("text/html; charset=utf-8");
            resp.getWriter().println("<h1>404</h1>");
        }else{
            Class<?> returnType = method.getReturnType();
            if(returnType == null){
                System.out.println("Controller层不能无返回");
            }else{
                // 参数处理
                String returnTypeSimpleName = returnType.getSimpleName();
                List parameList = RequsetParameter.handle(req,resp,method.getParameters());
                // 最后返回处理
                ReturnParameter.handle(returnTypeSimpleName,parameList,method,controller,resp);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

最后返回处理,根据返回类型给前端不同的响应

public class ReturnParameter {
    public static void handle(String returnTypeSimpleName, List parameList, Method method, Object controller, HttpServletResponse resp)throws Exception{
        if(returnTypeSimpleName.equals("String")){
            // 这里应该判断有没有body注解的,没有就返回页面,有返回字符串
            String res = (String) method.invoke(controller,parameList.toArray());
            resp.setContentType("text/plain;charset=UTF-8");
            resp.getWriter().println(res);
        }else if(returnTypeSimpleName.equals("List")){
            List list = (List) method.invoke(controller,parameList.toArray());
            String res = JSON.toJSONString(list);
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().println(res);
        }else if(returnTypeSimpleName.equals("Map")){
            Map map = (Map) method.invoke(controller,parameList.toArray());
            String res = JSON.toJSONString(map);
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().println(res);
        }
    }
}

到这里,接受请求,处理请求,返回响应整个流程都完成了,代码上传到github上

以上是关于手搓SSM的主要内容,如果未能解决你的问题,请参考以下文章

我用python_pygame手搓一个迷宫游戏

SSM-MyBatis-05:Mybatis中别名,sql片段和模糊查询加getMapper

手搓一个“七夕限定”,用3D Engine 5分钟实现烟花绽放效果

手搓线性回归

记录--ThreeJs手搓一个罗盘特效

手搓模版系列001-数值哈希/字符串哈希/字典树