【吃透Java手写】3-SpringBoot-简易版-源码解析
【吃透Java手写】SpringBoot-简易版-源码解析
- 1 SpringbootDemo
- 2 准备工作
- 2.1 Springboot-my
- 2.1.1 依赖
- 2.1.2 @SpringBootApplication
- 2.1.3 SJBSpringApplication
- 2.1.3.1 run方法
- 2.2 Springboot-user
- 2.2.1 依赖
- 2.2.2 UserController
- 2.2.3 UserApplication
- 2.3 分析run方法的逻辑
- 2.3.1 创建Spring容器
- 2.3.2 启动Tomcat
- 2.4 启动UserApplication
- 3 功能拓展
- 3.1 创建WebServer
- 3.2 获取对应服务的WebServer
- 3.3 条件注解@SJBConditionalOnClass
- 3.4 自动配置类
- 3.5 发现自动配置类
- 3.5.1 方法一 直接@Import
- 3.5.2 方法二 使用SPI
- 3.5.3 spring.factories源码解析
1 SpringbootDemo
引入依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.6.2</version></dependency>
</dependencies>
最简单的SpringbootDemo的只包含启动类
com.zhouyu.MyApplication:
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
我们手写自然要模拟MyApplication这个启动类所做的事情以及@SpringBootApplication
所做的事情
2 准备工作
两个包:
- springboot模块,表示springboot框架的源码实现
- user包,表示用户业务系统,用来写业务代码来测试我们所模拟出来的SpringBoot
2.1 Springboot-my
2.1.1 依赖
SpringBoot是基于的Spring,所以我们要依赖Spring,然后我希望我们模拟出来的SpringBoot也支持Spring MVC的那一套功能,所以也要依赖Spring MVC,包括Tomcat等,所以在Springboot-my模块中要添加以下依赖:
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.18</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version></dependency><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>9.0.60</version></dependency>
</dependencies>
我们在真正使用SpringBoot时,核心会用到SpringBoot一个类和注解:
- @SpringBootApplication,这个注解是加在应用启动类上的,也就是main方法所在的类
- SpringApplication,这个类中有个run()方法,用来启动SpringBoot应用的
所以我们也来模拟实现他们。
2.1.2 @SpringBootApplication
创建org.springboot.SJBSpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface SJBSpringBootApplication {
}
2.1.3 SJBSpringApplication
2.1.3.1 run方法
源码:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args);
}
String... args
:可变参数是为了让通过jar包启动的项目可以指定参数,比如-value=xxx等
我们模拟暂时不考虑这些,只保留最基础的功能
创建org.springboot.SJBSpringApplication
public class SJBSpringApplication {public static void run(Class<?> primarySource, String... args) {}
}
2.2 Springboot-user
我们模拟实现的是SpringBoot,而不是SpringMVC,所以我直接在user包下定义了UserController,最终我希望能运行UserApplication中的main方法,就直接启动了项目,并能在浏览器中正常的访问到UserController中的某个方法。
2.2.1 依赖
引入刚刚的依赖以及Springboot-my的基础依赖
<dependencies><dependency><groupId>org.example</groupId><artifactId>Springboot-my</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
2.2.2 UserController
创建com.sjb.user.controller.UserController
@RestController
public class UserController {@GetMapping("/test")public String test() {return "sjb123";}
}
2.2.3 UserApplication
创建com.sjb.user.UserApplication,使用我们刚刚创建的SJBSpringApplication启动类以及SJBSpringBootApplication注解
@SJBSpringBootApplication
public class UserApplication {public static void main(String[] args) {SJBSpringApplication.run(UserApplication.class, args);}
}
2.3 分析run方法的逻辑
首先,我们希望run方法一旦执行完,我们就能在浏览器中访问到UserController,那势必在run方法中要启动Tomcat,通过Tomcat就能接收到请求了。
大家如果学过Spring MVC的底层原理就会知道,在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法。
所以,在run方法中,我们要实现的逻辑如下:
- 创建一个Spring容器
- 创建Tomcat对象
- 生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
- 将DispatcherServlet添加到Tomcat中
- 启动Tomcat
2.3.1 创建Spring容器
public static void run(Class<?> configClazz) {AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(configClazz);applicationContext.refresh();
}
我们创建的是一个AnnotationConfigWebApplicationContext容器,并且把run方法传入进来的class作为容器的配置类,比如在UserApplication的run方法中,我们就是把2.2.3中的UserApplication.class传入到了run方法中,最终UserApplication就是所创建出来的Spring容器的配置类,并且由于UserApplication类上有@SJBSpringBootApplication注解,而@SJBSpringBootApplication注解的定义上又存在@ComponentScan注解,所以AnnotationConfigWebApplicationContext容器在执行refresh时,就会解析UserApplication这个配置类,从而发现定义了@ComponentScan注解,也就知道了要进行扫描,只不过扫描路径为空,而AnnotationConfigWebApplicationContext容器会处理这种情况,如果扫描路径会空,则会将UserApplication所在的包路径做为扫描路径,从而就会扫描到UserController。
RestController:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {@AliasFor(annotation = Controller.class)String value() default "";
}
Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {@AliasFor(annotation = Component.class)String value() default "";
}
所以Spring容器创建完之后,容器内部就拥有了UserController这个Bean。
2.3.2 启动Tomcat
我们用的是Embed-Tomcat,也就是内嵌的Tomcat,真正的SpringBoot中也用的是内嵌的Tomcat,而对于启动内嵌的Tomcat,也并不麻烦
在org.springboot.SJBSpringApplication#run中新建startTomcat方法
public static void startTomcat(WebApplicationContext applicationContext){Tomcat tomcat = new Tomcat();Server server = tomcat.getServer();Service service = server.findService("Tomcat");Connector connector = new Connector();connector.setPort(8081);Engine engine = new StandardEngine();engine.setDefaultHost("localhost");Host host = new StandardHost();host.setName("localhost");String contextPath = "";Context context = new StandardContext();context.setPath(contextPath);context.addLifecycleListener(new Tomcat.FixContextListener());host.addChild(context);engine.addChild(host);service.setContainer(engine);service.addConnector(connector);tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));context.addServletMappingDecoded("/*", "dispatcher");try {tomcat.start();} catch (LifecycleException e) {e.printStackTrace();}
}
代码虽然看上去比较多,但是逻辑并不复杂,比如配置了Tomcat绑定的端口为8081,后面向当前Tomcat中添加了DispatcherServlet,并设置了一个Mapping关系,最后启动,其他代码则不用太过关心。
而且在构造DispatcherServlet对象时,传入了一个ApplicationContext对象,也就是一个Spring容器,就是我们前文说的,DispatcherServlet对象和一个Spring容器进行绑定。
接下来,我们只需要在run方法中,调用startTomcat即可:
public static void run(Class<?> configClazz) {AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(configClazz);applicationContext.refresh();startTomcat(applicationContext);
}
2.4 启动UserApplication
输出
5月 06, 2024 1:40:03 下午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8081"]
5月 06, 2024 1:40:03 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Tomcat]
5月 06, 2024 1:40:03 下午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet engine: [Apache Tomcat/9.0.60]
5月 06, 2024 1:40:03 下午 org.apache.catalina.util.SessionIdGeneratorBase createSecureRandom
警告: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [111] milliseconds.
5月 06, 2024 1:40:03 下午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8081"]
显示启动成功,访问http://localhost:8081/test
成功访问到并输出
3 功能拓展
虽然我们前面已经实现了一个比较简单的SpringBoot,不过我们可以继续来扩充它的功能,比如现在我有这么一个需求,这个需求就是我现在不想使用Tomcat了,而是想要用Jetty,那该怎么办?
我们前面代码中默认启动的是Tomcat,那我现在想改成这样子:
- 如果项目中有Tomcat的依赖,那就启动Tomcat
- 如果项目中有Jetty的依赖就启动Jetty
- 如果两者都没有则报错
- 如果两者都有也报错
这个逻辑希望SpringBoot自动帮我实现,对于程序员用户而言,只要在Pom文件中添加相关依赖就可以了,想用Tomcat就加Tomcat依赖,想用Jetty就加Jetty依赖。
那SpringBoot该如何实现呢?
3.1 创建WebServer
我们知道,不管是Tomcat还是Jetty,它们都是应用服务器,或者是Servlet容器,所以我们可以定义接口来表示它们,这个接口叫做WebServer(别问我为什么叫这个,因为真正的SpringBoot源码中也叫这个)。
创建org.springboot.WebServer,并且在这个接口中定义一个start方法:
public interface WebServer { public void start();
}
有了WebServer接口之后,就针对Tomcat和Jetty提供两个实现类:
创建org.springboot.TomcatWebServer实现类和org.springboot.JettyWebServer实现类
public class TomcatWebServer implements WebServer{@Overridepublic void start() {System.out.println("TomcatWebServer start...");}
}
public class JettyWebServer implements WebServer{@Overridepublic void start() {System.out.println("JettyWebServer start...");}
}
并且在Springboot-my模块中添加Jetty的依赖
<dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.43.v20210629</version>
</dependency>
3.2 获取对应服务的WebServer
而在SJBSpringApplication中的run方法中,我们就要去获取对应的WebServer,然后启动对应的webServer,代码为:
public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();WebServer webServer = getWebServer(applicationContext);webServer.start();
}public static WebServer getWebServer(AnnotationConfigWebApplicationContext applicationContext){return null;
}
这样,我们就只需要在getWebServer方法中去判断到底该返回TomcatWebServer还是JettyWebServer。
前面提到过,我们希望根据项目中的依赖情况,来决定到底用哪个WebServer,所以直接用SpringBoot中的源码实现方式来模拟了。
3.3 条件注解@SJBConditionalOnClass
创建org.springboot.SJBConditionalOnClass注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(SJBOnClassCondition.class)
public @interface SJBConditionalOnClass {String value() default "";
}
-
@Conditional注解是从spring4.0才有的,可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。
-
Condition接口:用来表示条件判断的接口,源码如下:
@FunctionalInterface public interface Condition {/*** 判断条件是否匹配* context:条件判断上下文*/boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}
-
@Conditional使用的3步骤:
-
自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
-
在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型
-
启动spring容器加载资源,此时@Conditional就会起作用了
-
注意核心为@Conditional(SJBOnClassCondition.class)中的SJBOnClassCondition,因为它才是真正得条件逻辑:
public class SJBOnClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> annotationAttributes =metadata.getAnnotationAttributes(SJBConditionalOnClass.class.getName());String className = (String) annotationAttributes.get("value");try {context.getClassLoader().loadClass(className);return true;} catch (ClassNotFoundException e) {return false;}}
}
具体逻辑为,拿到@SJBConditionalOnClass中的value属性,然后用类加载器进行加载,如果加载到了所指定的这个类,那就表示符合条件,如果加载不到,则表示不符合条件。
并且进行可选依赖配置
将Spring-my中的依赖修改
<dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.43.v20210629</version><optional>true</optional>
</dependency>
添加<optional>true</optional>
这样只是Spring-user只依赖tomcat,如果要依赖jetty
则修改为
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.18</version><exclusions><exclusion><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId></exclusion></exclusions>
</dependency><!-- <dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>9.0.60</version>
</dependency>-->
<dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.43.v20210629</version>
</dependency>
3.4 自动配置类
UserApplication的@SJBSpringBootApplication只会扫描com.sjb.user下的包,自动配置类并没有作为Bean放入容器内
有了条件注解,我们就可以来使用它了,那如何实现呢?这里就要用到自动配置类的概念
创建org.springboot.WebServiceAutoConfiguration
@Configuration
public class WebServiceAutoConfiguration {@Bean@SJBConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer(){return new TomcatWebServer();}@Bean@SJBConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}
}
这个代码还是比较简单的,通过一个WebServiceAutoConfiguration的Spring配置类,在里面定义了两个Bean,一个TomcatWebServer,一个JettyWebServer,不过这两个要生效的前提是符合当前所指定的条件,比如:
- 只有存在"org.apache.catalina.startup.Tomcat"类,那么才有TomcatWebServer这个Bean
- 只有存在"org.eclipse.jetty.server.Server"类,那么才有TomcatWebServer这个Bean
并且我们只需要在SJBSpringApplication中getWebServer方法,如此实现:
public static WebServer getWebServer(ApplicationContext applicationContext){// key为beanName, value为Bean对象Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);if (webServers.isEmpty()) {throw new NullPointerException();}if (webServers.size() > 1) {throw new IllegalStateException();}// 返回唯一的一个return webServers.values().stream().findFirst().get();
}
这样整体SpringBoot启动逻辑就是这样的:
- 创建一个AnnotationConfigWebApplicationContext容器
- 解析UserApplication类,然后进行扫描
- 通过getWebServer方法从Spring容器中获取WebServer类型的Bean
- 调用WebServer对象的start方法
有了以上步骤,我们还差了一个关键步骤,就是Spring要能解析到WebServiceAutoConfiguration这个自动配置类,因为不管这个类里写了什么代码,Spring不去解析它,那都是没用的,此时我们需要SpringBoot在run方法中,能找到WebServiceAutoConfiguration这个配置类并添加到Spring容器中。
UserApplication是Spring的一个配置类,但是UserApplication是我们传递给SpringBoot,从而添加到Spring容器中去的,而WebServiceAutoConfiguration就需要SpringBoot去自动发现,而不需要程序员做任何配置才能把它添加到Spring容器中去,而且要注意的是,Spring容器扫描也是扫描不到WebServiceAutoConfiguration这个类的,因为我们的扫描路径是"com.sjb.user",而WebServiceAutoConfiguration所在的包路径为"org.springboot"。
3.5 发现自动配置类
3.5.1 方法一 直接@Import
直接在org.springboot.SJBSpringBootApplication注解上加上完成自动装配
@Import(WebServiceAutoConfiguration.class)
3.5.2 方法二 使用SPI
SpringBoot中自己实现了一套SPI机制,也就是我们熟知的spring.factories文件,那么我们模拟就不搞复杂了,就直接用JDK自带的SPI机制。
因为针对后面所有的自动配置类,一个一个导入不现实,所以就需要使用SPI。
然后需要在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件org.springboot.AutoConfiguration,在这个文件中写入接口的实现类的全限定名:
org.springboot.WebServiceAutoConfiguration
SPI的配置就完成了,相当于通过org.springboot.AutoConfiguration文件配置了springboot中所提供的配置类。
并且提供一个接口:
public interface AutoConfiguration {
}
并且WebServiceAutoConfiguration实现该接口:
@Configuration
public class WebServiceAutoConfiguration implements AutoConfiguration{@Bean@SJBConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer(){return new TomcatWebServer();}@Bean@SJBConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}
}
然后我们再利用spring中的@Import技术来导入这些配置类,我们在@SJBSpringBootApplication的定义上增加如下代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(SJBImportSelect.class)
public @interface SJBSpringBootApplication {
}
引入的SJBImportSelect如下,通过serviceLoader加载实现类并调用:
public class SJBImportSelect implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);List<String> list = new ArrayList<>();for (AutoConfiguration autoConfiguration : serviceLoader) {list.add(autoConfiguration.getClass().getName());}return list.toArray(new String[0]);}
}
ServiceLoader这个类的源码如下:
public final class ServiceLoader<S> implements Iterable<S> {//扫描目录前缀private static final String PREFIX = "META-INF/services/";// 被加载的类或接口private final Class<S> service;// 用于定位、加载和实例化实现方实现的类的类加载器private final ClassLoader loader;// 上下文对象private final AccessControlContext acc;// 按照实例化的顺序缓存已经实例化的类private LinkedHashMap<String, S> providers = new LinkedHashMap<>();// 懒查找迭代器private java.util.ServiceLoader.LazyIterator lookupIterator;// 私有内部类,提供对所有的service的类的加载与实例化private class LazyIterator implements Iterator<S> {Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;String nextName = null;//...private boolean hasNextService() {if (configs == null) {try {//获取目录下所有的类String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = loader.getResources(fullName);} catch (IOException x) {//...}//....}}private S nextService() {String cn = nextName;nextName = null;Class<?> c = null;try {//反射加载类c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {}try {//实例化S p = service.cast(c.newInstance());//放进缓存providers.put(cn, p);return p;} catch (Throwable x) {//..}//..}}
}
这就完成了从org.springboot.AutoConfiguration文件中获取自动配置类的名字,并导入到Spring容器中,从而Spring容器就知道了这些配置类的存在,而对于user项目而言,是不需要修改代码的。
3.5.3 spring.factories源码解析
@ComponentScan 注解只能扫描 Spring Boot 项目包内的 bean 并注册到 Spring 容器中,项目依赖包中的 bean 不会被扫描和注册。此时,我们需要使用 @EnableAutoConfiguration 注解来注册项目依赖包中的 bean。而 spring.factories 文件,可用来记录项目包外需要注册的 bean 类名。
EnableAutoConfiguration注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}
导入的类AutoConfigurationImportSelector.class:
根据接口获取其接口类的名称,这个方法返回的是类名的列表,获取所有的依赖包组成Map
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoader == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
在org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories中
然后从Map集合中找到Value,找他的META-INF/spring.factories,
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = (Map)cache.get(classLoader);if (result != null) {return result;} else {Map<String, List<String>> result = new HashMap();try {Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);
spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:
com.xxx.interface=com.xxx.classname
当在Maven中引入依赖时,Maven会负责下载并管理这些依赖包,但并不涉及具体的配置和集成。而当你在项目中需要使用Spring框架来管理组件、配置和依赖时,你可能需要在META-INF/spring.factories文件中注册一些类或者配置,以便Spring框架在应用启动时自动扫描并按照这些配置进行初始化。
所以,Maven用于管理依赖,而META-INF/spring.factories用于Spring框架的自动化配置和集成,它们可以配合使用来构建一个完整的项目。
需不需要META-INF这取决于依赖包本身的设计和功能。有些依赖包可能已经完全集成了Spring框架,并且在其内部已经做好了相应的自动化配置,因此你只需引入依赖即可直接在Spring项目中使用,而不需要手动添加额外的配置。
另一方面,有些依赖包可能提供了一些Spring框架的扩展或者功能,但并没有直接与Spring框架进行深度集成。在这种情况下,你可能需要在项目中手动配置一些内容,例如在META-INF/spring.factories文件中添加一些配置,以便Spring框架能够识别和使用这些扩展或功能。
因此,需要在META-INF中添加配置的情况取决于依赖包本身的特性以及与Spring框架的集成程度。
相关文章:

【吃透Java手写】3-SpringBoot-简易版-源码解析
【吃透Java手写】SpringBoot-简易版-源码解析 1 SpringbootDemo2 准备工作2.1 Springboot-my2.1.1 依赖2.1.2 SpringBootApplication2.1.3 SJBSpringApplication2.1.3.1 run方法 2.2 Springboot-user2.2.1 依赖2.2.2 UserController2.2.3 UserApplication 2.3 分析run方法的逻辑…...

maven mirrorOf的作用
在工作中遇到了一个问题导致依赖下载不了,最后发现是mirror的问题,决定好好去看一下mirror的配置,以及mirrorOf的作用,以前都是直接复制过来使用,看了之后才明白什么意思。 过程 如果你设置了镜像,镜像会匹…...

Centos7 安装 MySQL5.7 使用 RPM 方式
1 访问网站 https://downloads.mysql.com/archives/community/ 选择合适的版本,点击 Download。 2 上传下载好的 mysql-5.7.44-1.el7.x86_64.rpm-bundle.tar 文件到 Centos7 机器,这里放到了 下载 目录。 3 解压 mysql-5.7.44-1.el7.x86_64.rpm-bundle.…...
代码随想录算法训练营day21 | 513.找树左下角的值、112. 路径总和、106.从中序与后序遍历序列构造二叉树
513.找树左下角的值 迭代法比较简单,层序遍历,找到最下面一层的第一个节点。题目已经说明节点数>1了 class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:queue collections.deque()queue.append(root)result ro…...

微信小程序知识点归纳(一)
前言:适用于有一定基础的前端开发同学,完成从网页开发到小程序开发的知识转换。 先立框架,后砌墙壁 回顾:了解微信小程序开发流程-CSDN博客 初始页面结构,三部分pages、utils、配置,分别存放页面、工具类…...

wangEditor富文本编辑器与layui图片上传
记录:js 显示默认的wangEditor富文本编辑器内容和图片 <style>body {background-color: #ffffff;}.layui-form-select dl{z-index:100000;} </style> <div class"layui-form layuimini-form"><div class"layui-form-item"…...

爬虫学习:XPath提取网页数据
目录 一、安装XPath 二、XPath的基础语法 1.选取节点 三、使用XPath匹配数据 1.浏览器审查元素 2.具体实例 四、总结 一、安装XPath 控制台输入指令:pip install lxml 二、XPath的基础语法 XPath是一种在XML文档中查找信息的语言,可以使用它在HTM…...

【雅思写作】Vince9120雅思小作文笔记——P1 Intro(前言)
文章目录 链接P1 Intro(前言)字数限制题型综述(problem types overview)1. **柱状图(Bar Chart)** - 描述不同类别在某个或多个变量上的数据量比较。2. **线图(Line Graph)** - 展示…...
【面试干货】HTTPS 工作原理
【面试干货】HTTPS 工作原理 1、握手阶段(Handshake)2、密钥协商阶段3、加密通信阶段4、结束通信阶段 💖The Begin💖点点关注,收藏不迷路💖 HTTPS(HyperText Transfer Protocol Secureÿ…...
Cocos Creator 中编码规范 (6)
Cocos中命名规范 创建文件夹,全小写。创建脚本,首字母大写的驼峰形式。创建变量,首字母小写的驼峰形式 官方的编码规范...

Vue3:menu导航栏出现多个同一跳转路径的菜单处理
文章目录 需求整理实现思路实现过程 需求整理,实现思路 最近公司想将之前老的项目整理出来,因为这个老项目内容太杂什么页面都往里面塞,导致菜单特别多,公司就像将这个老的项目迁出来,这个旧的项目本来是后端PHP写的。…...

SAM轻量化应用Auto-SAM、Group-Mix SAM、RAP-SAM、STLM
1. Auto SAM(Auto-Prompting SAM for Mobile Friendly 3D Medical Image Segmentation) 1.1 面临问题 医学背景: (1)与自然图像相比,医学图像的尺寸更小,形状不规则,对比度更低。&…...
深度优化搜索DFS使用详解,看这篇就够了!!!
深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树和图的算法。在最坏的情况下,深度优先搜索的性能为O(VE),其中V是顶点数,E是边数。DFS常用于解决连通性问题、路径问题、生成树问题等。 ### D…...

Apache SeaTunnel 正式发布2.3.5版本,功能增强及多个Bug修复
经过两个月的筹备,我们在2.3.4版本基础上进行了新一轮的迭代,本次更新不仅修复了多个关键问题,还引入了若干重要功能增强和性能优化。 在此,我们先提前感谢社区成员的贡献和支持,如果你想升级最新的版本,快…...

interview_bak
flink内存管理 JVM 存在的几个问题: Java 对象存储密度低。一个只包含 boolean 属性的对象占用了16个字节内存:对象头占了8个,boolean 属性占了1个,对齐填充占了7个。而实际上只需要一个bit(1/8字节)就够了。Full GC 会极大地影响性能,尤其是为了处理更大数据而开了很大…...

layui 数据表格 自动定位新增行位置
由于数据表格新增行后没有到新增到当前位置 继续增加的需求: 因为自己是新增行后到最后一行的 所以 就定位到最后一行 并且 高亮 高亮颜色浅 可自行更改 整理了一下 可根据 情况 修改 // 初始化滚动条位置变量 let tableScroll {scrollTob: 0,scrollLeft: 0,…...

window10下安装ubuntu系统以及docker使用
window10下安装ubuntu系统以及docker使用 1. 启用适用于Linux的Windwos子系统2.下载Linux内核更新包3.将 WSL 2 设置为默认版本4.安装Ubuntu<br />直接去Microsoft store里面直接搜索Ubuntu进行安装。5.可能出现的问题1.win10启动ubuntu报错 参考的对象类型不支持尝试的操…...

Netty核心组件介绍
Netty是一款用于创建高性能网络应用程序的高级框架。Netty的核心组件如下: Channel回调Future事件和ChannelHander Channel channel是Java NIO的一个基本构造。可以把Channel看作是传入或传出数据的载体。它可以被打开或关闭,连接或断开连接。 回调 …...

代码审计平台sonarqube的安装及使用
docker搭建代码审计平台sonarqube 一、代码审计关注的质量指标二、静态分析技术分类三、使用sonarqube的目的四、sonarqube流程五、docker快速搭建sonarqube六、sonarqube scanner的安装和使用七、sonarqube对maven项目进行分析八、sonarqube分析报告解析九、代码扫描规则定制十…...
C++ 使用nlohmann/json.hpp库读写json字符串
1. json库 我个人比较喜欢 nlohmann/json.hpp 这个库,因为它只需要一个hpp文件即可,足够轻量! 这是它的github地址。 2. 简单实例代码 #include <iostream> #include <json.hpp> #include <fstream> #include <stri…...
【Elasticsearch】映射:fielddata 详解
映射:fielddata 详解 1.fielddata 是什么2.fielddata 的工作原理3.主要用法3.1 启用 fielddata(通常在 text 字段上)3.2 监控 fielddata 使用情况3.3 清除 fielddata 缓存 4.使用场景示例示例 1:对 text 字段进行聚合示例 2&#…...

uniapp Vue2 获取电量的独家方法:绕过官方插件限制
在使用 uniapp 进行跨平台应用开发时,获取设备电量信息是一个常见的需求。然而,uniapp 官方提供的uni.getBatteryInfo方法存在一定的局限性,它不仅需要下载插件,而且目前仅支持 Vue3,这让使用 Vue2 进行开发的开发者陷…...
如何把本地服务器变成公网服务器?内网ip网址转换到外网连接访问
内网IP只能在本地内部网络连接访问,当本地搭建服务器部署好相关网站或应用后,在局域网内可以通过内网IP访问,但在外网是无法直接访问异地内网IP端口应用的,只有公网IP和域名才能实现互联网上的访问。那么需要如何把本地服务器变…...

行为设计模式之Command (命令)
行为设计模式之Command (命令) 前言: 需要发出请求的对象(调用者)和接收并执行请求的对象(执行者)之间没有直接依赖关系时。比如遥控器 每个按钮绑定一个command对象,这个Command对…...

一次Oracle的非正常关闭
数据库自己会关闭吗? 从现象来说Oracle MySQL Redis等都会出现进程意外停止的情况。而这些停止都是非人为正常关闭或者暴力关闭(abort或者kill 进程) 一次测试环境的非关闭 一般遇到这种情况先看一下错误日志吧。 2025-06-01T06:26:06.35…...
C++ 中的 const 知识点详解,c++和c语言区别
目录 一。C 中的 const 知识点详解1. 基本用法1.1) 定义常量1.2) 指针与 const 2. 函数中的 const2.1)const 参数2.2)const 成员函数 3. 类中的 const3.1)const 成员变量3.2)const 对象 4. const 返回值5. …...

3. 简述node.js特性与底层原理
😺😺😺 一、Node.js 底层原理(简化版) Node.js 是一个 基于 Chrome V8 引擎构建的 JavaScript 运行时,底层核心由几部分组成: 组成部分简要说明 1.V8 引擎 将 JS 编译成机器码执行࿰…...
Oracle 用户名大小写控制
Oracle 用户名大小写控制 在 Oracle 数据库中,用户名的默认大小写行为和精确控制方法如下: 一 默认用户名大小写行为 不引用的用户名:自动转换为大写 CREATE USER white IDENTIFIED BY oracle123; -- 实际创建的用户名是 "WHITE"…...
Matlab | matlab中的图像处理详解
MATLAB 图像处理详解 这里写目录标题图像处理 MATLAB 图像处理详解一、图像基础操作1. 图像读写与显示2. 图像信息获取3. 图像类型转换二、图像增强技术1. 对比度调整2. 去噪处理3. 锐化处理三、图像变换1. 几何变换2. 频域变换四、图像分割1. 阈值分割2. 边缘检测3. 区域分割五…...
Java泛型中的通配符详解
无界通配符 通配符的必要性 通过WrapperUtil类的示例可以清晰展示通配符的使用场景。假设我们需要为Wrapper类创建一个工具类WrapperUtil,其中包含一个静态方法printDetails(),该方法需要处理任意类型的Wrapper对象。最初的实现尝试如下: …...