SpringMVC系列七: 手动实现SpringMVC底层机制-上
手动实现SpringMVC底层机制
- 博客的技术栈分析 🛠️
- 具体实现细节
- 总结
- 🐟准备工作
- 🍍搭建SpringMVC底层机制开发环境
- 实现任务阶段一
- 🍍开发ZzwDispatcherServlet
- 🥦说明: 编写ZzwDispatcherServlet充当原生的DispatcherServlet(即核心控制器)
- 🥦分析+代码实现
- 🥦配置Tomcat, 完成测试
- 实现任务阶段二
- 🍍完成客户端/浏览器可以请求控制层
- 🥦1.创建自己的Controller和自定义注解
- 🥦2.配置zzwspringmvc.xml
- 🥦3.编写XMLParser工具类, 可以解析zzwspringmvc.xml
- 🥦4.开发 ZzwWebApplicationContext, 充当Spring容器-得到扫描类的全路径列表.
- 🥦5.完善ZzwWebApplicationContext, 充当Spring容器-实例化对象到容器中
- 🥦6.完成请求URL和控制器方法的映射关系
- 🥦7.完成ZzwDispatcherServlet 分发请求到对应控制器方法
- 实现任务阶段三
- 🍍从web.xml动态获取zzwspringmvc.xml
- 实现任务阶段四
- 🍍完成自定义@Service注解功能
⬅️ 上一篇: SpringMVC系列六: 视图和视图解析器
🎉 欢迎来到 SpringMVC系列七: 手动实现SpringMVC底层机制-上 🎉
在本篇文章中,我们将深入探讨如何手动实现SpringMVC的底层机制。通过理解这些机制,可以更好地掌握SpringMVC的工作原理。
🔧 本篇需要用到的项目: zzw-springmvc项目
博客的技术栈分析 🛠️
主要技术
- 🌐 前端框架: 无(此博客主要关注于 SpringMVC 后端实现,因此未涉及具体前端框架)
- 🔧 后端框架: SpringMVC
- SpringMVC 是 Spring Framework 的一个模块,用于构建基于 MVC (Model-View-Controller) 架构的 web 应用程序。
- 博客中详细讲解了如何手动实现 SpringMVC 的核心机制,包括前端控制器、请求处理流程等。
- 📦 依赖管理: Maven
- Maven 是一个项目管理和构建工具,用于管理项目依赖和构建流程。
- 通过配置
pom.xml
文件来管理项目的依赖项,如 Servlet API、Junit 等。
- 📋 注解处理: 自定义注解
- 博客中使用了自定义注解(如
@Controller
和@RequestMapping
)来标识控制器类和方法,并通过反射实现注解处理。
- 博客中使用了自定义注解(如
辅助工具
- 📄 XML 配置: Dom4j
- Dom4j 是一个用于处理 XML 的开源 Java 库。博客中使用 Dom4j 解析
zzwspringmvc.xml
配置文件,以获取需要扫描的包路径。
- Dom4j 是一个用于处理 XML 的开源 Java 库。博客中使用 Dom4j 解析
- 🔍 反射 API: Java 反射
- Java 反射 API 被广泛用于动态获取类信息和调用方法。博客通过反射机制来扫描包、实例化类和调用控制器方法。
- 🗂️ 集合框架: ConcurrentHashMap
- 使用
ConcurrentHashMap
来存储 IoC 容器中的 bean 实例,确保线程安全。
- 使用
功能模块
- 核心控制器:
ZzwDispatcherServlet
- 继承
HttpServlet
类,通过覆盖doGet
和doPost
方法实现核心控制器功能,处理所有请求并将其分发到对应的控制器方法。
- 继承
- 自定义 IoC 容器:
ZzwWebApplicationContext
- 模拟 Spring 的 IoC 容器,扫描指定包路径下的类,并将带有注解的类实例化并存储到容器中。
- 请求映射处理:
ZzwHandler
- 维护 URL 与控制器方法的映射关系,并在请求到达时根据 URL 查找并调用对应的控制器方法。
具体实现细节
-
核心控制器 (
ZzwDispatcherServlet
)- 通过在
web.xml
中配置,将所有请求映射到ZzwDispatcherServlet
,实现统一的请求分发。
- 通过在
-
IoC 容器 (
ZzwWebApplicationContext
)- 扫描指定包路径下的类,判断是否包含特定注解(如
@Controller
,@Service
),并实例化这些类,存储到ConcurrentHashMap
中。
- 扫描指定包路径下的类,判断是否包含特定注解(如
-
请求映射 (
ZzwHandler
)- 使用自定义注解
@RequestMapping
指定控制器方法的 URL 映射,在请求到达时,通过 URL 找到对应的控制器方法并调用。
- 使用自定义注解
-
反射机制
- 通过反射获取类的元数据和注解信息,动态调用方法。
-
XML 解析
- 使用 Dom4j 解析 Spring 配置文件
zzwspringmvc.xml
,获取需要扫描的包路径,实现配置的灵活性。
- 使用 Dom4j 解析 Spring 配置文件
总结
本博客深入剖析了 SpringMVC 的底层实现机制,通过手动实现类似 SpringMVC 的功能,展示了 Java 反射、注解处理、XML 解析等技术的应用。通过这种方式,读者能够更好地理解 SpringMVC 的工作原理,提升自身的编程能力和框架理解能力。
🐟准备工作
🍍搭建SpringMVC底层机制开发环境
前提: 搭建maven环境
1.创建zzw-springmvc项目, 这是一个maven-web项目
出现了点小插曲. 项目建成后, 没有src目录, 且右下角报错
Cannot find JRE '1.7
做如下修改
改成1.8
缺少的文件夹需自己手动创建
pom.xml配置
<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><!--引入原生servlet依赖的jar包--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><!--解读1.scope标签表示引入的jar包的作用范围2.provided:表示该项目在打包, 放到生产环境时, 不需要带上servlet-api.jar包3.因为tomcat本身是有servlet的jar包, 到时直接使用tomcat本身的servlet-api.jar包, 防止版本冲突4.到后面会再次学习maven.--><scope>provided</scope></dependency><!--引入dom4j--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!--引入常用工具类的jar包-该jar包含有很多常用的类--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.5</version></dependency>
</dependencies>
实现任务阶段一
🍍开发ZzwDispatcherServlet
🥦说明: 编写ZzwDispatcherServlet充当原生的DispatcherServlet(即核心控制器)
🥦分析+代码实现
1.com.zzw.zzwspringmvc.servlet
包下新建ZzwDispatcherServlet.java
/*** 解读* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet* 2.本质是一个Servlet, 继承HttpServlet*/
public class ZzwDispatcherServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("ZzwDispatcherServlet doPost()...");}
}
2.src/main/resources
(类路径)下新建 zzwspringmvc.xml
, spring的容器配置文件
<!--先空着-->
对应的类路径
3.webapp/WEB-INF
配置web.xml
load-on-startup讲解
<!--配置ZzwDispatcherServlet, 作为我们自己的前端控制器-->
<servlet><servlet-name>ZzwDispatcherServlet</servlet-name><servlet-class>com.zzw.zzwspringmvc.servlet.ZzwDispatcherServlet</servlet-class><!--给ZzwDispatcherServlet配置参数, 指定要操作的spring容器配置文件--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:zzwspringmvc.xml</param-value></init-param><!--ZzwDispatcherServlet在tomcat启动时, 就会自动加载. 调用init方法--><load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><servlet-name>ZzwDispatcherServlet</servlet-name><!--因为ZzwDispatcherServlet作为前端控制器, 所以需要拦截所有请求--><url-pattern>/</url-pattern>
</servlet-mapping>
🥦配置Tomcat, 完成测试
1.配置tomcat
2.测试, 随便请求一个网址
实现任务阶段二
🍍完成客户端/浏览器可以请求控制层
🥦1.创建自己的Controller和自定义注解
示意图[分析说明]
1.在com.zzw.controller
下新建MonsterController
public class MonsterController {//编写方法, 可以列出怪物列表//springmvc 是支持原生的servlet api, 为了看到底层机制//这里我们涉及两个参数public void listMonster(HttpServletRequest request, HttpServletResponse response) {//设置返回编码和返回类型response.setContentType("text/html;charset=utf-8");//获取writer返回信息try {response.getWriter().write("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");} catch (IOException e) {throw new RuntimeException(e);}}
}
2.在com.zzw.zzwspringmvc.annotation
下新建注解类@Controller
RetentionPolicy.RUNTIME: 编译器把注解记录在class文件中, 当运行Java程序时, JVM 会保留注解. 程序可以通过反射获取该注解
/*** @author 赵志伟* @version 1.0* 该注解用于标识一个控制器组件* 这里涉及到注解知识, 在java基础*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {String value() default "";
}
3.在该包下新建注解类RequestMapping
/*** @author 赵志伟* @version 1.0* RequestMapping 注解用于指定控制器-方法的映射路径*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {String value() default "";;
}
4.在MonsterController
中添加注解
@Controller
public class MonsterController {//编写方法, 可以列出怪物列表//springmvc 是支持原生的servlet api, 为了看到底层机制//这里我们涉及两个参数@RequestMapping(value = "/monster/list")public void listMonster(HttpServletRequest request, HttpServletResponse response) {//设置返回编码和返回类型response.setContentType("text/html;charset=utf-8");//获取writer返回信息try {response.getWriter().write("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");} catch (IOException e) {throw new RuntimeException(e);}}
}
🥦2.配置zzwspringmvc.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans><!--指定要扫描的基本包以及子包的java类--> <component-scan base-package="com.zzw.controller"/>
</beans>
🥦3.编写XMLParser工具类, 可以解析zzwspringmvc.xml
Dom4j解析配置文件代码实现
1.在com.zzw.zzwspringmvc.xml
编写XMLParser
工具类, 可以解析zzwspringmvc.xml
, 得到要扫描的包
/*** @author 赵志伟* @version 1.0* XMLParser 用于解析spring配置文件*/
@SuppressWarnings({"all"})
public class XMLParser {public static String getBasePackage(String xmlFile) {//1.得到解析器SAXReader reader = new SAXReader();//2.得到类的加载路径 => 获取到spring配置文件[对应的资源流]InputStream inputStream =XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);try {//3.得到xml文件的文档Document document = reader.read(inputStream);//4.获取rootElementElement rootElement = document.getRootElement();//5.获取component-scan节点Element componentScanElement =(Element) rootElement.elements("component-scan").get(0);//6.获取component-scan节点的base-package属性值String basePackage = componentScanElement.attributeValue("base-package");//7.返回return basePackage;} catch (Exception e) {throw new RuntimeException(e);}}
}
2.在com.zzw.test
新建ZzwSpringMVCTest.java
测试类
XMLParser类
在很多包下都有, 别选错
public class ZzwSpringMVCTest {@Testpublic void readXML() {String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");System.out.println(basePackage);}
}
🥦4.开发 ZzwWebApplicationContext, 充当Spring容器-得到扫描类的全路径列表.
把指定的目录包括子目录下的java
类的全路径扫描到集合中, 比如 ArrayList
[java基础]
示意图[分析说明]
1.在com.zzw.zzwspringmvc.context
下新建ZzwWebApplicationContext.java
/*** @author 赵志伟* @version 1.0* ZzwWebApplicationContext 表示我们自己的spring容器*/
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {//定义属性classFullPathList, 保存扫描包/子包的类的全路径private List<String> classFullPathList =new ArrayList<String>();//编写方法, 完成自己的spring容器的初始化public void init() {String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");scanPackage(basePackage);}/*** 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理* @param pack 表示要扫描的包, 比如 com.zzw.controller*/public void scanPackage(String pack) {//通过类的加载器, 得到指定的包所在的工作路径对应的绝对路径//比如 com.zzw.controller => url = file:/D:/idea_project/zzw_springmvczzw-springmvc/target/classes/com/zzw/controllerClassLoader classLoader = this.getClass().getClassLoader();URL url = classLoader.getResource(pack.replace(".", "/"));//细节说明:// 1.不要直接使用Junit测试, 否则 url返回null// 2.启动Tomcat测试, 才能得到这个类路径System.out.println("url=" + url);}
}
2.前端控制器ZzwDispatcherServlet
增加init
方法
/*** 解读* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet* 2.本质是一个Servlet, 继承HttpServlet*/
public class ZzwDispatcherServlet extends HttpServlet {@Overridepublic void init(ServletConfig config) throws ServletException {super.init(config);ZzwWebApplicationContext zzwWebApplicationContext =new ZzwWebApplicationContext();zzwWebApplicationContext.init();}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("ZzwDispatcherServlet doPost()...");}
}
3.启动Tomcat, 进行测试
4.开发自己的spring容器
/*** @author 赵志伟* @version 1.0* ZzwWebApplicationContext 表示我们自己的spring容器*/
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {//定义属性classFullPathList, 保存扫描包/子包的类的全路径private List<String> classFullPathList =new ArrayList<String>();//编写方法, 完成自己的spring容器的初始化public void init() {String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");scanPackage(basePackage);System.out.println("classFullPathList=" + classFullPathList);}/*** 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理** @param pack 表示要扫描的包, 比如 com.zzw.controller*/public void scanPackage(String pack) {//通过类的加载器, 得到指定的包所在的工作路径对应的绝对路径//比如 com.zzw.controller => url = file:/D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/ClassLoader classLoader = this.getClass().getClassLoader();URL url = classLoader.getResource(pack.replace(".", "/"));//细节说明:// 1.不要直接使用Junit测试, 否则 url返回null// 2.启动Tomcat测试, 才能得到这个类路径System.out.println("url=" + url);//根据得到的路径, 对其进行扫描, 把类的全路径保存到classFullPathListString path = url.getFile();File dir = new File(path);//在io中, 目录也是文件//遍历dir[文件/子目录]for (File f : dir.listFiles()) {if (f.isDirectory()) {//如果是一个目录, 需要递归扫描scanPackage(pack + "." + f.getName());//f.getName() 子包的名称} else {//说明: 这时, 你扫描到的文件, 可能是.class文件, 也可以是其它文件// 就算是.class文件, 也存在是不是需要注入到容器中的问题// 目前先把所有.class文件的全路径都保存到集合中, 后面在注入对象到容器时, 再处理// 这里只考虑 .class文件String classFullPath = pack + "." + f.getName().replaceAll(".class", "");classFullPathList.add(classFullPath);}}}
}
5.在com.zzw.controller.xx
包下新建GoodsController, OrderController
.
6.重启Tomcat
, 测试
🥦5.完善ZzwWebApplicationContext, 充当Spring容器-实例化对象到容器中
功能说明: 将扫描到的类, 在满足条件的情况下(即有相应的注解@Controller @Service...
时), 反射到ioc容器.
1.ZzwWebApplicationContext
增加ioc
属性. 增加executeInstance
方法
/*** @author 赵志伟* @version 1.0* ZzwWebApplicationContext 表示我们自己的spring容器*/
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {//定义属性ioc, 存放反射生成的bean对象public ConcurrentHashMap<String, Object> ioc =new ConcurrentHashMap<String, Object>();//编写方法, 完成自己的spring容器的初始化public void init() {String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");scanPackage(basePackage);System.out.println("classFullPathList=" + classFullPathList);//将扫描到的类, 反射到ioc容器executeInstance();System.out.println("扫描后的 ioc容器 " + ioc);}//编写方法, 将扫描到的类, 在满足条件的情况下, 反射到ioc容器public void executeInstance() {//判断是否扫描到类if (classFullPathList.size() == 0) {//说明没有扫描到类return;}try {//遍历classFullPathList, 进行反射for (String classFullPath : classFullPathList) {Class<?> clazz = Class.forName(classFullPath);//说明当前这个类有@Controllerif (clazz.isAnnotationPresent(Controller.class)) {//beanName 假设是默认的, 即类名首字母小写String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);ioc.put(beanName, clazz.newInstance());}//如果有其它注解, 可以拓展!!}} catch (Exception e) {throw new RuntimeException(e);}}
}
我这里输出的时候乱码, 我的解决方案是. 全改成UTF-8
测试
🥦6.完成请求URL和控制器方法的映射关系
功能说明: 将配置的@RequestMapping
的url
和 对应的 控制器-方法 映射关系保存到集合中
示意图[分析说明]
1.在com.zzw.zzwspringmvc.handler
下新建ZzwHandler
/*** @author 赵志伟* @version 1.0* ZzwHandler 对象记录请求的url 和 控制器方法映射关系*/
@SuppressWarnings({"all"})
public class ZzwHandler {private String url;private Object controller;private Method method;public ZzwHandler(String url, Object controller, Method method) {this.url = url;this.controller = controller;this.method = method;}//getter, setter, toString方法
}
2.修改ZzwDispatcherServlet
- 将
init
方法内声明的zzwWebApplicationContext
属性提到外面, 扩大它的作用域 - 定义属性
handlerList
, 保存ZzwHandler
[url 和 控制器-方法的映射关系] - 编写方法[initHandlerMapping], 完成url 和 控制器-方法的映射 (initHandlerMapping也可以写在HandlerMapping类中, 逻辑是一样的)
/*** 解读* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet* 2.本质是一个Servlet, 继承HttpServlet* 3.提示: 这里我们需要使用到 java web 讲解的Servlet*/
public class ZzwDispatcherServlet extends HttpServlet {//定义属性 handlerList, 保存ZzwHandler[url 和 控制器-方法的映射关系]private List<ZzwHandler> handlerList= new ArrayList<ZzwHandler>();//定义属性 zzwWebApplicationContext, 自己的spring容器ZzwWebApplicationContext zzwWebApplicationContext = null;@Overridepublic void init(ServletConfig config) throws ServletException {super.init(config);zzwWebApplicationContext = new ZzwWebApplicationContext();zzwWebApplicationContext.init();//调用 initHandlerMapping, 完成url和控制器方法的映射initHandlerMapping();System.out.println("handlerList初始化的结果=" + handlerList);}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("ZzwDispatcherServlet doPost()...");}//编写方法, 完成url 和 控制器方法的映射private void initHandlerMapping() {if (zzwWebApplicationContext.ioc.isEmpty()) {//判断当前的ioc容器是否为空return;}//遍历ioc容器的bean对象, 然后进行url映射处理//java基础 map遍历for (Map.Entry<String, Object> entry : zzwWebApplicationContext.ioc.entrySet()) {//先取出实例, 转化为clazz对象[要获取类的内部信息, 类的实例对象不好用, 要用类的Class对象 反射知识]Class<?> clazz = entry.getValue().getClass();//如果注入的bean是Controllerif (clazz.isAnnotationPresent(Controller.class)) {//取出它所有的方法Method[] declaredMethods = clazz.getDeclaredMethods();//遍历方法for (Method declaredMethod : declaredMethods) {//判断该方法是否有@RequestMappingif (declaredMethod.isAnnotationPresent(RequestMapping.class)) {//取出@RequestMapping值 -> 就是映射路径RequestMapping requestMappingAnnotation =declaredMethod.getDeclaredAnnotation(RequestMapping.class);String url = requestMappingAnnotation.value();//创建ZzwHandler对象->就是一个映射关系 [保存映射关系]ZzwHandler zzwHandler =new ZzwHandler(url, entry.getValue(), declaredMethod);//放入到 handlerListhandlerList.add(zzwHandler);}}}}}
}
🥦7.完成ZzwDispatcherServlet 分发请求到对应控制器方法
功能说明: 完成ZzwDispatcherServlet 分发请求到对应控制器方法
示意图[分析说明]
-当用户发出请求, 根据用户请求url 找到对应的 控制器-方法, 并反射调用
-如果用户请求的路径不存在, 返回404
1.ZzwDispatcherServlet
添加getZzwHandler()
方法和executeDispatcher()
方法, 在doPost
中调用 executeDispatcher()
方法
public class ZzwDispatcherServlet extends HttpServlet {@Overridepublic void init(ServletConfig config) throws ServletException {super.init(config);//创建自己的spring容器zzwWebApplicationContext = new ZzwWebApplicationContext();zzwWebApplicationContext.init();//调用 initHandlerMapping, 完成url和控制器方法的映射initHandlerMapping();System.out.println("handlerList初始化的结果=" + handlerList);}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//System.out.println("ZzwDispatcherServlet doPost()...");//调用方法, 完成请求转发executeDispatcher(req, resp);}//编写方法, 通过request对象, 返回ZzwHandler对象//如果没有, 就返回nullprivate ZzwHandler getZzwHandler(HttpServletRequest request) {//1.先获取到用户请求的url 比如http://localhost:8080/zzw_springmvc/monster/list// uri = /zzw_springmvc/monster/list//2.这里要注意得到的uri 和 保存的url 有一个工程路径的问题//两个方案解决 =>第一个方案: 简单 tomcat 直接配置 application context => /// 第二个方案: 保存 zzwHandler对象的url时, 拼接 this.getServletContext().getContextPath()String requestURI = request.getRequestURI();//遍历 handlerListfor (ZzwHandler zzwHandler : handlerList) {if (requestURI.equals(zzwHandler.getUrl())) {//说明匹配成功return zzwHandler;}}return null;}//编写方法, 完成分发请求任务private void executeDispatcher(HttpServletRequest request,HttpServletResponse response) {try {ZzwHandler zzwHandler = getZzwHandler(request);if (zzwHandler == null) {//说明用户请求的路径/资源不存在response.getWriter().print("<h1>404 NOT FOUND!</h1>");} else {//匹配成功, 反射调用控制器的方法zzwHandler.getMethod().invoke(zzwHandler.getController(), request, response);}} catch (Exception e) {throw new RuntimeException(e);}}
}
2.OrderController
增加两个方法listOrder(), addOrder()
别忘了加Controller注解
@Controller
public class OrderController {@RequestMapping(value = "/order/list")public void listOrder(HttpServletRequest request, HttpServletResponse response) {//设置返回编码和返回类型response.setContentType("text/html;charset=utf8");//获取writer返回信息try {response.getWriter().write("<h1>订单列表信息</h1>");} catch (IOException e) {throw new RuntimeException(e);}}@RequestMapping(value = "/order/add")public void addOrder(HttpServletRequest request, HttpServletResponse response) {//设置返回编码和返回类型response.setContentType("text/html;charset=utf8");//获取writer返回信息try {response.getWriter().write("<h1>添加订单信息</h1>");} catch (IOException e) {throw new RuntimeException(e);}}
}
3.GoodsController
增加一个方法listGoods()
@Controller
public class GoodsController {@RequestMapping(value = "/goods/list")public void listGoods(HttpServletRequest request, HttpServletResponse response) {//设置返回编码和返回类型response.setContentType("text/html;charset=utf8");//获取writer返回信息try {response.getWriter().write("<h1>商品列表信息...</h1>");} catch (IOException e) {throw new RuntimeException(e);}}
}
4.测试(注意: 不要再加工程路径了)
handlerList初始化的结果=
[ZzwHandler{url=‘/goods/list’, controller=com.zzw.controller.xx.GoodsController@79b1752f, method=public void com.zzw.controller.xx.GoodsController.listGoods(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
ZzwHandler{url=‘/order/add’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.addOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
ZzwHandler{url=‘/order/list’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
ZzwHandler{url=‘/monster/list’, controller=com.zzw.controller.MonsterController@32128628, method=public void com.zzw.controller.MonsterController.listMonster(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)}]
实现任务阶段三
🍍从web.xml动态获取zzwspringmvc.xml
说明: 前面我们加载zzwspringmvc.xml是硬编码, 现在做活. 从web.xml动态获取
示意图[分析说明]
1.ZzwDispatcherServlet
在创建并初始化ZzwWebApplicationContext
时, 动态地从web.xml
中获取到spring
配置文件.
servletConfig使用
/*** 解读* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet* 2.本质是一个Servlet, 继承HttpServlet* 3.提示: 这里我们需要使用到 java web 讲解的Servlet*/
public class ZzwDispatcherServlet extends HttpServlet {@Overridepublic void init(ServletConfig servletConfig) throws ServletException {//获取到web.xml中的/*<init-param><param-name>contextConfigLocation</param-name><param-value>classpath:zzwspringmvc.xml</param-value></init-param>*/String configLocation = servletConfig.getInitParameter("contextConfigLocation");//创建自己的spring容器zzwWebApplicationContext = new ZzwWebApplicationContext(configLocation);zzwWebApplicationContext.init();//调用 initHandlerMapping, 完成url和控制器方法的映射initHandlerMapping();System.out.println("handlerList初始化的结果=" + handlerList);}.......
}
2.ZzwWebApplicationContext.java
中添加一个属性configLocation, 和一个无参构造器, 一个有参构造器, 并修改init()方法
/*** @author 赵志伟* @version 1.0* ZzwWebApplicationContext 表示我们自己的spring容器*/
public class ZzwWebApplicationContext {//定义属性classFullPath, 保存扫描包/子包的类的全路径private List<String> classFullPathList= new ArrayList<String>();//定义属性ioc, 存放反射生成的bean对象 有Controller/Service注解public ConcurrentHashMap<String, Object> ioc= new ConcurrentHashMap<String, Object>();//创建一个属性, 表示spring容器配置文件private String configLocation; //添加一个无参构造器public ZzwWebApplicationContext() {}//构建一个有参构造器public ZzwWebApplicationContext(String configLocation) {this.configLocation = configLocation;}//编写方法, 完成自己的spring容器的初始化public void init() {//这里我们写的是固定的spring容器配置文件 => 做活//String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);scanPackage(basePackage);System.out.println("basePackage=" + basePackage);System.out.println("classFullPathList=" + classFullPathList);//将扫描到的类, 反射到ioc容器executeInstance();System.out.println("扫描后的 ioc容器=" + ioc);}........
}
3.测试…
实现任务阶段四
🍍完成自定义@Service注解功能
说明: 如果给某个类加上@Service
, 则可以将其注入到我们的Spring容器
示意图[分析说明]
补充: DAO和DB由MyBatis接管, 和SpringMVC关系并不大. 所以我们暂时不考虑DAO和DB.
1.在com.zzw.entity
包下新建Monster
public class Monster {private Integer id;private String name;private String skill;private Integer age;//全参构造器, getter, setter, toString方法
}
2.在com.zzw.zzwspringmvc.annotation
下新建@Service
. 这个注解是springmvc框架要支持的东西, 所以要在zzwspringmvc包下
/*** @author 赵志伟* @version 1.0* Service 注解, 用于标识一个Service对象, 并注入到spring容器*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {String value() default "";
}
3.在com.zzw.service
下新建MonsterService接口
.
public interface MonsterService {//增加方法-返回monster列表public List<Monster> listMonster();
}
3.1在com.zzw.service.impl
新建MonsterServiceImpl实现类
. 并标注@Service
, 表示可以将对象注入到Spring
容器
/*** @author 赵志伟* @version 1.0* MonsterServiceImpl 作为一个Service注入到spring容器*/
@SuppressWarnings({"all"})
public class MonsterServiceImpl implements MonsterService {//这里我们模拟数据->DBpublic List<Monster> listMonster() {List<Monster> monsters = new ArrayList<Monster>();monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));return monsters;}
}
3.2完善zzwspringmvc.xml
, 加上com.zzw.service
<?xml version="1.0" encoding="UTF-8" ?>
<beans><!--指定要扫描的基本包以及子包的java类--> <component-scan base-package="com.zzw.controller,com.zzw.service"/>
</beans>
3.3更改ZzwWebApplicationContext.java
的init()
//编写方法, 完成自己的spring容器的初始化public void init() {//这里我们写的是固定的spring容器配置文件 => 做活//String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");String basePackage =XMLParser.getBasePackage(configLocation.split(":")[1]);//这时我们的basePackage => com.zzw.controller,com.zzw.service//scanPackage(basePackage);String[] basePackages = basePackage.split(",");if (basePackages.length > 0) {for (String pack : basePackages) {scanPackage(pack);}}........}
4.ZzwWebApplicationContext
的executeInstance
增加一个else if分支. 并可以通过接口支持多级-类名来获取到Service Bean
//编写方法, 将扫描到的类, 在满足条件的情况下, 反射到ioc容器
public void executeInstance() {//判断是否扫描到类if (classFullPathList.size() == 0) {//说明没有扫描到类return;}try {//遍历classFullPathList, 进行反射for (String classFullPath : classFullPathList) {Class<?> clazz = Class.forName(classFullPath);//说明当前这个类有@Controller注解if (clazz.isAnnotationPresent(Controller.class)) {//beanName 假设是默认的, 即类名首字母小写String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);ioc.put(beanName, clazz.newInstance());}//如果有其它注解, 可以拓展!! 处理@Serviceelse if (clazz.isAnnotationPresent(Service.class)) {//如果类有@Service//先获取到@Service的value值 => 就是注入时的beanNameService serviceAnnotation = clazz.getDeclaredAnnotation(Service.class);String beanName = serviceAnnotation.value();if ("".equals(beanName)) {//说明没有指定value, 我们就使用默认的机制注入Service//可以通过 接口名/类名[首字母小写] 来注入ioc容器//1.得到所有接口的名称=>接口Class<?>[] interfaces = clazz.getInterfaces();Object instance = clazz.newInstance();//2.遍历接口, 然后通过多个接口名来注入for (Class<?> anInterface : interfaces) {//接口名->首字母小写String beanName2 = anInterface.getSimpleName().substring(0, 1).toLowerCase()+ anInterface.getSimpleName().substring(1);ioc.put(beanName2, instance);}//3.这里老师给留了个作业: 使用类名的首字母小写来注入bean// 通过 clazz 来获取即可.String beanName2 = clazz.getSimpleName().substring(0, 1).toLowerCase()+ clazz.getSimpleName().substring(1);ioc.put(beanName2, instance);} else {//如果有指定名称, 就使用该名称注入即可ioc.put(beanName, clazz.newInstance());}}}} catch (Exception e) {throw new RuntimeException(e);}
}
5.测试-重启tomcat
扫描后的 ioc容器={goodsController=com.zzw.controller.xx.GoodsController@5fb9a20e, monsterService=com.zzw.service.impl.MonsterServiceImpl@3b03f989, monsterServiceImpl=com.zzw.service.impl.MonsterServiceImpl@3b03f989, orderController=com.zzw.controller.xx.OrderController@2f51e8b1, monsterController=com.zzw.controller.MonsterController@7a223f3b}
🔜 下一篇预告 🔜
敬请期待:SpringMVC系列八: 手动实现SpringMVC底层机制-下
📚 目录导航 📚
- SpringMVC系列一: 初识SpringMVC
- SpringMVC系列二: 请求方式介绍
- SpringMVC系列三: Postman(接口测试工具)
- SpringMVC系列四: Rest-优雅的url请求风格
- SpringMVC系列五: SpringMVC映射请求数据
- SpringMVC系列六: 视图和视图解析器
- SpringMVC系列七: 手动实现SpringMVC底层机制-上
- SpringMVC系列八: 手动实现SpringMVC底层机制-下
…
💬 读者互动 💬
在学习SpringMVC底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。
相关文章:
SpringMVC系列七: 手动实现SpringMVC底层机制-上
手动实现SpringMVC底层机制 博客的技术栈分析 🛠️具体实现细节总结 🐟准备工作🍍搭建SpringMVC底层机制开发环境 实现任务阶段一🍍开发ZzwDispatcherServlet🥦说明: 编写ZzwDispatcherServlet充当原生的DispatcherSer…...
嵌入式web 服务器boa的编译和移植
编译环境:虚拟机 ubuntu 18.04 目标开发板:飞凌OKA40i-C开发板, Linux3.10 操作系统 开发板本身已经移植了boa服务器,但是在使用过程中发现POST方法传输大文件时对数据量有限制,超过1M字节就无法传输,这是…...
什么是js?特点是什么?组成部分?
Js是一种直译式脚本语言,一种动态类型,弱类型,基于原型的高级语言。 直译式:js程序运行过程中直接编译成机器语言。 脚本语言:在程序运行过程中逐行进行解释说明,不需要预编译。 动态类型:js…...
Java 面试题:如何保证集合是线程安全的? ConcurrentHashMap 如何实现高效地线程安全?
在多线程编程中,保证集合的线程安全是一个常见而又重要的问题。线程安全意味着多个线程可以同时访问集合而不会导致数据不一致或程序崩溃。在 Java 中,确保集合线程安全的方法有多种,包括使用同步包装类、锁机制以及并发集合类。 最简单的方法…...
打工人的PPT救星来了!用这款AI工具,10秒生成您的专属PPT
今天帮同事解决了一个代码合并的问题。其实问题不复杂,要把1的代码合到2的位置: 这个处理方式其实很简单,使用 “git cherry-pick hash值” 就可以。 同事直接对我赞许有加,不曾想被领导看到了,对我说了一句ÿ…...
GIT 合拼
合拼有多种方式: 1)合拼分支: git merge [source-branch] 2)合拼提交 : git cherry-pick [commit-hash] 3)合拼单个文件: git checkout [source-branch] – [file] 以上合拼,比如将分…...
利用 Python 和 AI 技术制作智能问答机器人
利用 Python 和 AI 技术制作智能问答机器人 引言 在人工智能的浪潮下,智能问答机器人成为了一种非常实用的技术。它们能够处理大量的查询,提供即时的反馈,并且可以通过机器学习技术不断优化自身的性能。本文将介绍如何使用 Python 来开发一…...
electron系列(一)调用dll
用electron的目的,其实很简单。就是web架构要直接使用前端电脑的资源,但是浏览器限制了使用,所以用electron来达到这个目的。其中调用dll是一个非常基本的操作。 安装 ffi-napi 和 ref-napi 包: npm install ffi-napi ref-napi main.js&…...
VUE3实现个人网站模板源码
文章目录 1.设计来源1.1 网站首页页面1.2 个人工具页面1.3 个人日志页面1.4 个人相册页面1.5 给我留言页面 2.效果和源码2.1 动态效果2.2 目录结构 源码下载万套模板,程序开发,在线开发,在线沟通 作者:xcLeigh 文章地址࿱…...
C语言 | Leetcode C语言题解之第162题寻找峰值
题目: 题解: int findPeakElement(int* nums, int numsSize) {int ls_max0;for(int i1;i<numsSize;i){if(nums[ls_max]>nums[i]);else{ls_maxi;}}return ls_max; }...
利用pickle保存和加载对象
使用 pickle.dump 保存下来的文件可以使用 pickle.load 打开和读取。以下是一个示例,展示了如何使用 pickle 模块保存和加载对象: 保存对象 import pickle# 假设有一个对象 obj obj {"key": "value"}# 将对象保存到文件 with ope…...
定制汽车霍尔传感器
磁电效应霍尔传感器、饱和霍尔传感器、非线性霍尔传感器 霍尔传感器原理 霍尔传感器的工作原理基于霍尔效应,即当一块通有电流的金属或半导体薄片垂直地放在磁场中时,薄片的两端会产生电位差。这种现象称为霍尔效应,两端具有的电位差值称为…...
【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的巡演(100分) - 三语言AC题解(Python/Java/Cpp)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 …...
ChatGPT 简介
ChatGPT 是一种基于大型语言模型的对话系统,由 OpenAI 开发。它的核心是一个深度学习模型,使用了 GPT(Generative Pre-trained Transformer)架构。以下是 ChatGPT 的原理和工作机制的详细介绍: ### GPT 架构 1. **Tr…...
大数据实训室建设可行性报告
一、建设大数据实训室的背景与意义 随着信息技术的飞速发展,大数据已成为推动社会进步和经济发展的重要力量。中高职院校作为技能型人才培养的摇篮,承担着为社会输送大数据领域高素质、高技能人才的重要任务。因此,建设大数据实训室…...
学懂C#编程:让函数返回 多个返回值 的几种常用技术
1. 使用 out 或 ref 参数 out 和 ref 参数允许方法修改传入变量的值,并通过它们“返回”多个值。ref 需要变量事先初始化,而 out 不要求。 public void GetValues(out int val1, out string val2) {val1 10;val2 "Hello"; }// 使用示例 int…...
蔚来汽车AI算法工程师,如何理解注意力?
大家好啊,我是董董灿。 今天分享一个上海蔚来汽车的AI算法岗位面试经验总结帖,面试岗位为算法工程师。 这次面试提到的问题,除了与实习相关内容和反问之外,面试官总共问了8个问题,主要集中在深度学习基础概念的理解上…...
信创适配评测
概叙 信创科普参考:全面国产化之路-信创-CSDN博客 有必要再解释一下两个名词“28N”,“79号文件”,因为“28N”指定了由政府牵头从各领域开启国产化的基调,而“79号文件”则指定了国产化的截止日期2027年。 信创的本质是实现中国信…...
【Qt6.3 基础教程 04】探索Qt项目结构和配置文件
文章目录 前言Qt项目的基本结构配置文件:.pro文件基本构成示例.pro文件: qmake和构建过程步骤简述: 修改项目设置结论 前言 当你开始使用Qt进行开发时,理解项目结构和配置文件的作用是至关重要的。这篇博文将带你深入了解Qt项目的…...
SpringBoot测试实践
测试按照粒度可分为3层: 单元测试:单元测试(Unit Testing)又称为模块测试 ,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中…...
Flask-OAuthlib
Flask-OAuthlib库教程 Flask-OAuthlib 是一个为 Flask 应用提供 OAuth1 和 OAuth2 支持的库。它允许开发者轻松地集成第三方 OAuth 服务,或者构建自己的 OAuth 提供者服务。 官方文档链接 Flask-OAuthlib官方文档 架构概述 Flask-OAuthlib 的主要组件包括&…...
树和森林.
目录 一、树 1.1树的存储结构 1.1.1双亲表示法 1.1.2孩子链表 1.1.3孩子兄弟表示法 1.2树与二叉树的转换 1.2.1将树转换成二叉树: 1.2.2将二叉树转换成树 二、森林 2.1森林与二叉树的转换 2.1.1将森林转换成二叉树 2.1.2二叉树转换成森林 三、树和森林的…...
ubuntu下同时安装和使用不同版本的库 librealsense
apt 安装的最新版本在/usr 源码安装的旧版本在/usr/local set(realsense2_DIR /usr/local/) find_package(realsense2 2.50.0 REQUIRED) message( "\n\n ${realsense2_INCLUDE_DIR} ${realsense2_VERSION} RealSense SDK 2.0 is FINDINGING, please install it from…...
openEuler操作系统下静默安装Oracle19c
在openEuler-23.09上安装Oracle19c,创建非容器数据库实例(含静默安装) 操作系统版本 openEuler-23.09-x86_64-dvd.iso ,安装步骤此处省略。。。 最常用且直接的方法来查看openEuler的版本号是查看/etc/os-release文件 [root@openEuler ~]$ cat /etc/os-release NAME="…...
Linux CPU常见命令行详解
在Linux系统中,命令行是管理和监控系统资源的重要工具。特别是当我们需要了解CPU的状态、性能和利用率时,一系列命令行工具就显得尤为重要。本文将详细介绍Linux中与CPU相关的常见命令行工具及其使用方法,帮助大家更好地理解和利用这些工具来…...
防止更新或保存 Laravel 模型
例如,创建模型后,我不希望任何人能够再次更新该记录。相反,它应该被全新的记录覆盖并存档。 这是一个简单的特征,您可以在模型上使用它来禁用更新: trait PreventsUpdating {public static function bootPreventsUpd…...
Cadence:Conformal系列形式验证工具
Conformal 工具最早由Verplex Systems开发。Verplex是一家专注于形式验证工具开发的公司,其核心产品是Conformal等效性检查工具。由于其技术的先进性和市场需求,Verplex的 Conformal工具迅速在半导体行业内获得了认可。 2003 年,Cadence Desi…...
一般人不要学Python?一般人怎么学Python!!
关于“建议一般人真的不要学Python”这一观点,我认为这是一个过于绝对的说法。实际上,Python作为一种流行的编程语言,具有许多优点,适合不同背景和需求的人学习。以下是一些反驳这一观点的理由: 易于学习和理解&#x…...
微服务架构中间件安装部署
微服务架构中间件安装部署 jdk安装 安装包jdk-8u144-linux-x64.tar.gz 先检查系统原版本的jdk并卸载 rpm -qa | grep java 显示信息如下: tzdata-java-2014g-1.el6.noarch java-1.6.0-openjdk-1.6.0.0-11.1.13.4.el6.x86_64 java-1.7.0-openjdk-1.7.0.65-2.5.1.2.…...
车辆数据的提取、定位和融合(其一 共十二篇)
第一篇: System Introduction 第二篇:State of the Art 第三篇:localization 第四篇:Submapping and temporal weighting 第五篇:Mapping of Point-shaped landmark data 第六篇:Clustering of landma…...
Vue3组件通信全解析:利用props、emit、provide/inject跨层级传递数据,expose与ref实现父子组件方法调用
文章目录 一、父组件数据传递N个层级的子组件vue3 provide 与 injectA组件名称 app.vueB组件名称 provideB.vueC组件名称 provideCSetup.vue 二、使用v-model指令实现父子组件的双向绑定父组件名称 app.vue子组件名称 v-modelSetup.vue 三、父组件props向子组件传值子组件 prop…...
华为---OSPF被动接口配置(四)
9.4 OSPF被动接口配置 9.4.1 原理概述 OSPF被动接口也称抑制接口,成为被动接口后,将不会接收和发送OSPF报文。如果要使OSPF路由信息不被某一网络中的路由器获得且使本地路由器不接收网络中其他路由器发布的路由更新信息,即已运行在OSPF协议…...
前端将Markdown文本转换为富文本显示/编辑,并保存为word文件
参考:https://www.wangeditor.com/ https://blog.csdn.net/weixin_43797577/article/details/138854324 插件: markdown-it traptitech/markdown-it-katex markdown-it-link-attributes highlight.js wangeditor/editor wangeditor/editor-for-vue html…...
git-shortlog详解
作用 git-shortlog - Summarize git log output 语法 git shortlog [<options>] [<revision-range>] [[--] <path>…] git log --prettyshort | git shortlog [<options>] 功能描述 Summarizes git log output in a format suitable for inclus…...
通过MATLAB实现PID控制器,积分分离控制器以及滑模控制器
目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 通过MATLAB实现PID控制器,积分分离控制器以及滑模控制器。通过对比三个算法可知,采用滑模控制算法,其具有最快的收敛性能,较强的鲁棒性&…...
Node.js 渲染三维模型并导出为图片
Node.js 渲染三维模型并导出为图片 1. 前言 本文将介绍如何在 Node.js 中使用 Three.js 进行 3D 模型渲染。通过结合 gl 和 canvas 这两个主要依赖库,我们能够在服务器端实现高效的 3D 渲染。这个方法解决了在服务器端生成和处理 3D 图形的需求,使得可…...
Win11下安装VS2022失败的解决办法
前几天我把我的HP Z840的操作系统换成了Win11,在重装VS2022时遇到了麻烦,提示无法安装 Microsoft.VisualStudio.Devenv.Msi。 查看安装日志提示:Could not write value devenv.exe to key \SOFTWARE\Microsoft\Internet Explorer\Main\Featur…...
动态规划:基本概念
Dynamic Programming 动态规划(Dynamic Programming, DP) 是一种算法设计技巧,通常用来解决具有重叠子问题和最优子结构性质的问题。它通过将问题分解为更小的子问题,逐步解决这些子问题并将结果存储起来,以避免重复计…...
小山菌_代码随想录算法训练营第二十九天| 455. 分发饼干 、376. 摆动序列、53. 最大子序和
455. 分发饼干 文档讲解:代码随想录.分发饼干 视频讲解:贪心算法,你想先喂哪个小孩?| LeetCode:455.分发饼干 状态:已完成 代码实现 class Solution { public:int findContentChildren(vector<int>&…...
快手可灵大模型开放视频续写功能,可生成最长约3分钟视频
6月21日,可灵再度进化,正式推出图生视频功能,支持用任意静态图像生成5s视频,并且可搭配不同的文本内容,实现丰富的视觉叙事 。 同时,可灵还发布了业内领先的视频续写功能,可为已生成的视频&…...
【代码随想录】【算法训练营】【第45天】 [198]打家劫舍 [213]打家劫舍II [337]打家劫舍III
前言 思路及算法思维,指路 代码随想录。 题目来自 LeetCode。 day 45,周五,坚持不了一点~ 题目详情 [198] 打家劫舍 题目描述 198 打家劫舍 解题思路 前提: 思路: 重点: 代码实现 C语言 虚拟头…...
python安装目录文件说明----Dlls文件夹
在Python的安装目录下,通常会有一个DLLs文件夹,它是Python标准库的一部分。这个文件夹包含了一些动态链接库(Dynamic Link Libraries,DLL),这些库提供了Python解释器和标准库的一些关键功能。以下是对这个文…...
java实现持续集成
要使用Java实现Jenkins持续集成,你可以使用Jenkins的Java客户端库来执行一些常见的操作,例如创建任务,触发构建等。下面是一个简单的示例代码,展示了如何使用Java实现Jenkins持续集成: java import com.offbytwo.jenk…...
ClickHouse安装与下载22.3.2.2
ClickHouse安装与下载 目录 1. ClickHouse简介 1.1 ClickHouse优点: 1.2 ClickHouse缺点: 1.3 ClickHouse引擎: 1.3.1 数据库引擎 1.3.2 表引擎 2. ClickHouse下载安装 2.1 ClickHouse下载安装 2.2 ClickHouse使用 1. ClickHouse简…...
【Go语言】Gin 框架教程
Gin 框架教程 1.第一个 Gin 程序 1.1 Gin 安装 # 执行执行如下操作即可,安装Gin前需要安装Go环境 go get -u -v github.com/gin-gonic/gin # -v:打印出被构建的代码包的名字 # -u:已存在相关的代码包,强行更新代码包及其依赖包…...
MySQL性能问题诊断方法和常用工具
作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG数据库运维(如安装迁移,性能优化、故障应急处理等) 公众号:老苏畅谈运维 欢迎关注本人公众号,更多精彩与您分享。MySQL运…...
CGFloat转NSString保持原有的精度,末尾不添加0
问题阐述: 我们进行CGFloat转NSString可能会遇到一个问题 例如有一个CGFloat的值为2.1,转化成NSString后显示2.1000... 解决办法: 方法一: 如何解决呢,可以使用%g格式符,可以保证传入的不管是2还是2.1…...
UDS服务——TransferData (0x36)
诊断协议那些事儿 诊断协议那些事儿专栏系列文章,本文介绍TransferData (0x36)—— 数据传输,用于下载/上传数据时用的,数据的传输方向由不同的服务控制:0x34服务表示下载,0x35服务表示上传。通过阅读本文,希望能对你有所帮助。 文章目录 诊断协议那些事儿传输数据服务…...
jQuery 基本操作
01-简介 jQuery 是一个功能丰富且广泛使用的 JavaScript 库,它简化了 HTML 文档遍历和操作、事件处理、动画和 Ajax 操作。jQuery 通过其易用的 API,使复杂的 JavaScript 编程任务变得更加简单,并且兼容各种浏览器。 1、jQuery特点 简化 DOM …...
有玩家在2011年的MacBook上成功运行了Windows XP 还安装了触摸屏
我们已经在许多不同的设备上看到过 Windows XP 正在运行。这个古老的操作系统于 2001 年正式推出,现在已经老到其最后一次软件更新是在近十年前。一位好奇的玩家试图在 2011 年的触摸屏 MacBook 上为 Windows XP 打造了一个新家,复古技术探索者 Michael …...