您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

熬夜手写个SpringMVC框架

时间:2022-03-24 14:18:55  来源:  作者:Darker.

在spring全家桶流行的当下,只要你做的是JAVA技术栈基本上95%以上都使用的spring或springboot框架,剩下的5%基本上是一些老项目,政府项目,银行项目之类对安全性要求比较高的项目,比如之前前段时间的log4j2,Spring Getway远程代码执行漏洞;总有意想不到的BUG,所以有些公司也会自己封装框架。那么我们也根据SpringMVC的基本实现原理,基于Servlet类封装一个自己的MVC框架。

进入正题:

聊聊技术选型:

为了是框架足够简单且可以实现mvc基本功能,我们这里只引用少量的外部类库,项目基于JDK8,JavaWeb,并采用Maven的方式进行构建。

采用的设计模式:策略模式,观察者模式

使用技术:JavaEE,Maven,反射技术

项目结构如下:

com.kexun
  annotation #mvc及orm所使用的到的注解
  controller #基于框架的示例代码
  dao #数据库操作类,存放sql
  db #orm框架源码
  entity #实现类
  mvc #mvc框架实现源码
  utils #依赖的工具类

基本实现逻辑:

  1. 实现容器监听器,项目启动时扫描@ReqrestMApping标记的方法以请求路径为key 对应的方法Method为value put到map,并同时初始化参数解析器
  2. 创建统一拦截Servlet,根据请求路径去map寻找对应的处理器方法
  3. 通过反射获取所要调用的方法参数,遍历参数解析器寻找合适的解析器方法,对参数进行解析注入
  4. controller处理相关业务逻辑,并标记相应注解
  5. 根据注解标记判断是转发到页面还是返回文本数据

代码实现:

  1. 创建容器类,初始化时的数据
public class ContAIners {
    //用于存储controller对应的路径及方法对象
    public static HashMap<String, Method> mappingMethods = new HashMap<>();
    //存储参数解析器
    public static List<BaseResolver> resolvers = new ArrayList<>();
}

2.初始化mapping 和 resolvers

public class InitMapping implements ServletContextListener {   
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        //扫描controller包到容器
        Set<Class<?>> classes = ClassUtils.getClasses("com.kexun.controller");
        //遍历controller包下所有的类
        for (Class<?> aClass : classes) {
            //获取类注解RequestMapping
            RequestMapping classMapping = aClass.getAnnotation(RequestMapping.class);
            String classMappingVal = null;
            if (classMapping != null) {
                //对类上的RequestMapping值进行拼接 并对多余的 / 进行处理
                classMappingVal = classMapping.value().replace("/", "");
            }
             //反射获取类所有方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                RequestMapping mapping = method.getAnnotation(RequestMapping.class);
                //获取并且判断是否存在RequestMapping 注解 如不存在则不做操作
                if (mapping != null) {
                    String methodMappingVal = mapping.value().replace("/", "");
                    //String key = "/" + classMappingVal + "/" + methodMappingVal;
                    String key = "";
                    //组装方法请求路径 
                    if (classMappingVal != null) {
                        key += ("/" + classMappingVal);
                    }
                    key += ("/" + methodMappingVal);
                    boolean b = Containers.mappingMethods.containsKey(key);
                    if (b) {
                        throw new RuntimeException("存在相同方法路径" + key);
                    } else {
                        System.out.println("初始化Controller:" + key);
                        //put到map容器
                        Containers.mappingMethods.put(key, method);
                    }
                }
            }
        }
        //初始化解析器到容器
        Containers.resolvers.add(new IntResolver());
        Containers.resolvers.add(new BaseEntityResolver());
        Containers.resolvers.add(new HttpServletRequestResolver());
        Containers.resolvers.add(new HttpServletResponseResolver());
        Containers.resolvers.add(new HttpSessionResolver());
        Containers.resolvers.add(new StringResolver());
        Containers.resolvers.add(new RequestBodyResolver());
    }
}

3.参数解析器

根据目标方法的参数类型进行解析,我这里实现了几个常用的参数类型解析器,如需扩展可以实现 BaseResolver接口

//解析策略接口
public interface BaseResolver {
    boolean supportsParameter(Parameter parameter);
    Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp);
}

boolean supportsParameter方法:判断参数是否符合预期类型

Object resolveArgument方法:完成参数的解析逻辑

各解析器实现如下:



//String类型参数解析器
public class StringResolver implements BaseResolver {
    @Override
    public boolean supportsParameter(Parameter parameter) {
        return parameter.getType().isAssignableFrom(String.class);
    }


    @Override
    public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
        return req.getParameter(parameter.getName());
    }
}
//int类型的参数解析器
public class IntResolver implements BaseResolver {
    @Override
    public boolean supportsParameter(Parameter parameter) {
        return parameter.getType().isAssignableFrom(int.class) || parameter.getType().isAssignableFrom(Integer.class);
    }


    @Override
    public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("设置参数:" + parameter.getName());
        return Integer.parseInt(req.getParameter(parameter.getName()));
    }
}




//有标记 RequestBody 参数的解析
public class RequestBodyResolver implements BaseResolver {
    @Override
    public boolean supportsParameter(Parameter parameter) {
        return parameter.getAnnotation(RequestBody.class) != null;
    }


    @Override
    public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
        String bodyData = getBodyData(req);
        System.out.println("body:"+bodyData);
        try {
          //将JSON类型的参数映射到Jav实体类
            return JSON.toJavaObject(JSON.parseobject(bodyData), parameter.getType().newInstance().getClass());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    //获取请求body数据
    private String getBodyData(HttpServletRequest request) {
        StringBuffer data = new StringBuffer();
        String line = null;
        BufferedReader reader = null;
        try {
            reader = request.getReader();
            while (null != (line = reader.readLine())) {
                data.append(line);
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
        return data.toString();
    }
}


//HttpSession类型参数解析器
public class HttpSessionResolver implements BaseResolver {
    @Override
    public boolean supportsParameter(Parameter parameter) {
        return parameter.getType().isAssignableFrom(HttpSession.class);
    }


    @Override
    public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
        return req.getSession();
    }
}
//HttpServletResponse 类型的参数解析器
public class HttpServletResponseResolver implements BaseResolver {
    @Override
    public boolean supportsParameter(Parameter parameter) {
        return parameter.getType().isAssignableFrom(HttpServletResponse.class);
    }


    @Override
    public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
        return resp;
    }
}


//HttpServletRequest类型的参数解析器
public class HttpServletRequestResolver implements BaseResolver {
    @Override
    public boolean supportsParameter(Parameter parameter) {
        return parameter.getType().isAssignableFrom(HttpServletRequest.class);
    }


    @Override
    public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
        return req;
    }
}
//BaseEntity 实现了BaseEntity的类的解析器 解析参数到实体类
public class BaseEntityResolver implements BaseResolver {
    @Override
    public boolean supportsParameter(Parameter parameter) {
        try {
            return parameter.getType().newInstance() instanceof BaseEntity;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }


    @Override
    public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
        Enumeration<String> parameterNames = req.getParameterNames();
        Object o = null;
        try {
            o = parameter.getType().newInstance();
            while (parameterNames.hasMoreElements()) {
                String s = parameterNames.nextElement();
                String parameter1 = req.getParameter(s);
                BeanUtils.setProperty(o, s, parameter1);
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
        return o;
    }
}

4.注册Servlet拦截所有请求,并根据请求路径分发到对应处理器方法 代码实现如下:

public class WebServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        Enumeration<String> parameterNames = req.getParameterNames();
        String contextPath = req.getServletPath();
        System.out.println(contextPath);
        Method method = Containers.mappingMethods.get(contextPath);
        if (method == null) {
            //请求路径不存在时 返回404 也可以重定向或转发到404页面
            resp.sendError(404, "页面不存在");
        } else {
            //获取请求类型
            String contentType = req.getContentType();
            String method1 = req.getMethod();
            System.out.println("method:" + method1 + " contentType:" + contentType);
            try {
                //获取方法所在的类并实例化 
                Object o = method.getDeclaringClass().newInstance();
                //获取方法所有的对象
                Parameter[] parameters = method.getParameters();
                //存放方法参数解析注入后的值 参数列表
                ArrayList<Object> params = new ArrayList<>();
              //获取对应参数解析器,注入参数
                for (Parameter parameter : parameters) {
                    Object o2 = null;
                    //获取解析器
                    List<BaseResolver> resolvers = Containers.resolvers;
                    for (BaseResolver resolver : resolvers) {
                        //判断是否符合预期类型
                        if (resolver.supportsParameter(parameter)) {
                        //符合 进行解析
                            o2 = resolver.resolveArgument(parameter, req, resp);
                            break;
                        }
                    }
                    //保存解析后的值
                    params.add(o2);


                }
                //根据方法所在类及所需参数通过反射调用方法
                Object invoke = method.invoke(o, params.toArray());
                if (invoke != null) {
                    //判断方法是否标记了ResponseBody注解
                    ResponseBody annotation = method.getAnnotation(ResponseBody.class);
                    if (annotation != null) {
                        //有 ResponseBody 注解并且是String类型 直接响应请求
                        if (invoke instanceof String) {
                            resp.setContentType("text/html");
                            resp.setCharacterEncoding("UTF-8");
                            // 获取PrintWriter对象
                            PrintWriter out = resp.getWriter();
                            out.print(invoke);
                            // 释放PrintWriter对象
                            out.flush();
                            out.close();
                        } else {
                            //如果不是String类型则装换成JSON格式字符串在进行响应
                            resp.setContentType("application/json; charset=utf-8");
                            resp.setCharacterEncoding("UTF-8");
                            // 获取PrintWriter对象
                            PrintWriter out = resp.getWriter();
                            out.print(JSON.toJSONString(invoke));
                            // 释放PrintWriter对象
                            out.flush();
                            out.close();
                        }


                    } else {
                        //没有标记注解 返回String类型 则转发到返回字符串所对应的jsp页面
                        if (invoke instanceof String) {
                            RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/" + invoke + ".jsp");
                            dispatcher.forward(req, resp);
                        } else {
                            new RuntimeException("返回值不是String类型 且没有@ResponseBody注解");
                        }
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

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

5.所用到的注解标记

RequestBody

@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
}

RequestMapping

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}

ResponseBody

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}

6.所用到工具类

ClassUtils



public class ClassUtils {
    public static Set<Class<?>> getClasses(String pack) {


        // 第一个class类的集合
        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageName = pack;
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(
                    packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    System.err.println("file类型的扫描");
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath,
                            recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 定义一个JarFile
                    System.err.println("jar类型的扫描");
                    JarFile jar;
                    try {
                        // 获取jar
                        jar = ((JarURLConnection) url.openConnection())
                                .getJarFile();
                        // 从此jar包 得到一个枚举类
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同样的进行循环迭代
                        while (entries.hasMoreElements()) {
                            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/开头的
                            if (name.charAt(0) == '/') {
                                // 获取后面的字符串
                                name = name.substring(1);
                            }
                            // 如果前半部分和定义的包名相同
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"结尾 是一个包
                                if (idx != -1) {
                                    // 获取包名 把"/"替换成"."
                                    packageName = name.substring(0, idx)
                                            .replace('/', '.');
                                }
                                // 如果可以迭代下去 并且是一个包
                                if ((idx != -1) || recursive) {
                                    // 如果是一个.class文件 而且不是目录
                                    if (name.endsWith(".class")
                                            && !entry.isDirectory()) {
                                        // 去掉后面的".class" 获取真正的类名
                                        String className = name.substring(
                                                packageName.length() + 1, name
                                                        .length() - 6);
                                        try {
                                            // 添加到classes
                                            classes.add(Class
                                                    .forName(packageName + '.'
                                                            + className));
                                        } catch (ClassNotFoundException e) {
                                            // log
                                            // .error("添加用户自定义视图类错误 找不到此类的.class文件");
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        // log.error("在扫描用户定义视图时从jar包获取文件出错");
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


        return classes;
    }


    public static void findAndAddClassesInPackageByFile(String packageName,
                                                        String packagePath, final boolean recursive, Set<Class<?>> classes) {
        // 获取此包的目录 建立一个File
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            @Override
            public boolean accept(File file) {
                return (recursive && file.isDirectory())
                        || (file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件
        for (File file : dirfiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "."
                                + file.getName(), file.getAbsolutePath(), recursive,
                        classes);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0,
                        file.getName().length() - 6);
                try {
                    // 添加到集合中去
                    //classes.add(Class.forName(packageName + '.' + className));
                    //经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
                    e.printStackTrace();
                }
            }
        }
    }


}

7.web.xml 配置:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


    <display-name>Archetype Created Web Application</display-name>
    <!--启动监听器配置-->
    <listener>
        <listener-class>com.kexun.mvc.listen.InitMapping</listener-class>
    </listener>
     <!--统一拦截Servlet配置-->
    <servlet>
        <servlet-name>webServlet</servlet-name>
        <servlet-class>com.kexun.mvc.servlet.WebServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--静态资源默认处理Servlet-->
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.Apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
     <!--静态资源默认处理Servlet-->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/static/*</url-pattern>
    </servlet-mapping>
     <!--统一拦截servlet 拦截所有请求-->
    <servlet-mapping>
        <servlet-name>webServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>




</web-app>

8.项目所用到的类库

 

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>


        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>


        <dependency>
            <groupId>MySQL</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>


        <dependency>
            <groupId>org.apache.Tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>8.5.61</version>
        </dependency>


    </dependencies>

 

9.基本使用

使用方法基本与SpringMvc一致,注解大同小异 。

示例代码如下:



public class IndexController {


    //获取字符串,int类型参数 返回页面
    @RequestMapping("index")
    public String index(HttpServletRequest request, String username, Integer age) throws Exception {
        request.setAttribute("username", username);
        request.setAttribute("age", age);
        return "index";
    }
     
     //获取对象参数 返回对象
    @ResponseBody
    @RequestMapping("addManage")
    public Map<String, Object> addManage(Manage manage) throws Exception {
        System.out.println("manage:" + manage);
        Map<String, Object> result = new HashMap<>();
        result.put("code", 0);
        result.put("message", "添加成功");
        return result;
    }
    //获取JSON请求 解析为对象 返回对象类型
    @ResponseBody
    @RequestMapping("addManageJSON")
    public Map<String, Object> addManageJSON(@RequestBody Manage manage) throws Exception {
        System.out.println("manage:" + manage);
        Map<String, Object> result = new HashMap<>();
        result.put("code", 0);
        result.put("message", "添加成功");
        return result;
    }




}

 

此框架实用与一些小项目,案例,需要快速搭建的,开箱即用的场景;没有复杂的配置,同时也适用于学习,不允许适用开源框架的场景下,这便是个很好的选择

项目源码已上传至码云:

https://gitee.com/gdianqimeng/kexun-mvc-orm

个人博客地址:https://muzidong.com



Tags:SpringMVC框架   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
熬夜手写个SpringMVC框架
在spring全家桶流行的当下,只要你做的是Java技术栈基本上95%以上都使用的spring或springboot框架,剩下的5%基本上是一些老项目,政府项目,银行项目之类对安全性要求比较高的项目,...【详细内容】
2022-03-24  Search: SpringMVC框架  点击:(292)  评论:(0)  加入收藏
▌简易百科推荐
Qt与Flutter:在跨平台UI框架中哪个更受欢迎?
在跨平台UI框架领域,Qt和Flutter是两个备受瞩目的选择。它们各自具有独特的优势,也各自有着广泛的应用场景。本文将对Qt和Flutter进行详细的比较,以探讨在跨平台UI框架中哪个更...【详细内容】
2024-04-12  刘长伟    Tags:UI框架   点击:(7)  评论:(0)  加入收藏
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(9)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(22)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(60)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(50)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(41)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(56)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(71)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(92)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(89)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条