SpringBoot深入理解

项目打包SpringBoot启动过程

当使用打包时,会下载org-springframework-boot-loader的jar,并且不会放在lib存放的第三方jar包文件中,该jar包中有个JarLauncher.class文件中设置了jar包运行时的入口和打包后文件的结构(定义了BOOT-INF,META-INF中放什么数据)

使用java -jar 启动项目时,因为META-INF中文件记载了启动的顺序

Manifest-Version: 1.0		#版本
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Start-Class: com.zyy.gradletest1.GradleTest1Application #项目的启动器
Spring-Boot-Classes: BOOT-INF/classes/ #记录编译后文件存放地址
Spring-Boot-Lib: BOOT-INF/lib/ #记录第三方jar包存放地址
Spring-Boot-Version: 2.3.0.RELEASE #SpringBoot版本
Main-Class: org.springframework.boot.loader.JarLauncher #标记了程序的入口,程序的入口定义类加载器去加载项目的启动器

所以程序会直接进入JarLauncher.class中执行main方法

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
} protected JarLauncher(Archive archive) {
super(archive);
} @Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
} public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
} }

JarLancher继承了ExecutableArchiveLauncher,ExecutableArchiveLauncher抽象类是归档启动器的父类,它有两个子类

​ Lancher:是整个归档的基类(抽象类)

​ |

ExecutableArchiveLauncher

​ | |

JarLancher WarLancher

顾名思义jarLancher就是打包成jar的归档启动类,WarLancher是War打包方式的归档启动类。

当执行JarLancher的main方法时,会调用Lancher基类的launch方法,该方法注释为:启动应用程序。 此方法是初始入口点应该由一个子类被称为public static void main(String[] args)方法。


使用SpringBoot自定义类加载器(LaunchedURLClassLoader)来加载打包好的Jar文件中BOOT-INF里面的类和lib

ClassLoader classLoader = createClassLoader(getClassPathArchives());

getClassPathArchives()方法:返回嵌套Archive S表示匹配指定过滤器条目。

意思就是:获得需要自定义类加载器需要加载的类的路径并返回(归档文件)

public abstract class ExecutableArchiveLauncher extends Launcher {
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(
/*this::isNestedArchive:确定指定JarEntry是应添加到类路径嵌套项。 该方法被调用一次为每个条目。
说人话就是查看当前需要加载的路径是否符合,如果符合返回true,这个方法调用的就是JarLancher类 中的isNestedArchive方法*/
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
//返回Archive类型的集合,Archive是可以由{@link Launcher}启动的档案类,里面记录的需要加载类的路径
return archives;
}
}

补充知识:

正常的jar包加载的方式就是直接找到classpath下的顶层路径加载(例如:org.springframework)

传统的打包方式是将第三方包和自己写的代码打包在一块,这就有可能出现,包名冲突之类的。为了解决这种问题,Spring的打包方式就是把第三方包和自己的代码分开。但是此时就出现了一个问题,不能正常找到顶层的加载文件,那么spring就出现了org-springframework-boot-loader包,spring默认规定该包在打包时将该包放到classpath下(顶层路径),首先加载该类中对应的Lancher类,然后通过该类创建的自定义类加载器去加载项目中其他的类。


准备去加载项目的主运行程序

launch(args, getMainClass(), classLoader);

getMainClass():获得ExecutableArchiveLauncher.Archive类中的清单,Archive在创建自定义类加载器时,被构造方法初始化,初始化的路径就是classpath路径下的META-INF文件中的信息然后读取对应的主程序入口的路径(也就是META-INF文件中的信息)key为Start-Class的值(就是项目主程序的启动器)

public abstract class ExecutableArchiveLauncher extends Launcher {

	private final Archive archive;

	public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
}

当所有参数准备完毕之后,进入launch方法中

public abstract class Launcher {
/**
* Launch the application given the archive file and a fully configured classloader.
* @param args the incoming arguments
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception if the launch fails
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
//将当前线程的上下文设置为自定义类加载器
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
}

创建一个运行Main的县城,将运行产参数和类加载器传入进去

createMainMethodRunner(mainClass, args, classLoader)

/**
* Create the {@code MainMethodRunner} used to launch the application.
* @param mainClass the main class main方法运行的路径
* @param args the incoming arguments 方法的参数
* @param classLoader the classloader 自定义类加载器
* @return the main method runner 返回线程
*/
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}

当线程创建好之后!!!重点来了!!!!运行run方法

createMainMethodRunner(mainClass, args, classLoader).run();

public void run() throws Exception {
//获得线程上下文取出类加载器,然后获得要运行的main方法的路径
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
//通过反射定位到main方法的位置
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
//调用对应的main方法(此时调用的就是SpringBoot项目的启动器)
mainMethod.invoke(null, new Object[] { this.args });
}

注意:mainMethod.invoke(null, new Object[] { this.args });

​ 为什么invoke第一个参数是空,因为main方法是static的,static不归存与类中,它只是把类当做寄存的场所而已,原因是所有的方法必须存在与类中

jar包启动时,使用-agentlib:jdwp远程debug

什么是jdwep?

JDWP 是 Java Debug Wire Protocol 的缩写,它定义了调试器(debugger)和被调试的 Java 虚拟机(target vm)之间的通信协议,可以项目在线运行时,远程debug查看运行的流程,如果项目是部署在tomcat中运行,需要在tomcat中配置相关的启动,才能使用远程debug

启动jar包时,使用命令

然后在idea中配置

注意:这种调试需要在配置之前在idea中有相同的代码,然后打上断点

注解

java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):

@Documented – 注解是否将包含在JavaDoc中

@Retention – 什么时候使用该注解

@Target – 注解用于什么地方

@Inherited – 是否允许子类继承该注解

1.)@Retention – 定义该注解的生命周期

RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。

RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式

RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括

ElementType.CONSTRUCTOR: 用于描述构造器

ElementType.FIELD: 成员变量、对象、属性(包括enum实例)

ElementType.LOCAL_VARIABLE: 用于描述局部变量

ElementType.METHOD: 用于描述方法

ElementType.PACKAGE: 用于描述包

ElementType.PARAMETER: 用于描述参数

ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。

4.)@Inherited – 定义该注释和子类的关系

@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

@Configuration官方文档

修饰一个类为配置类,将Bean注入到Spring容器中,在运行期间生成bean的定义

@Configuration
public class AppConfig { @Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}

@Configuration类通常使用AnnotationConfigApplicationContext或者支持web的AnnotationConfigWebApplicationContext的引导,来使用@Bean

//在test类中可以使用,或者直接使用@Autowired自动注入
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
MyBean myBean = ctx.getBean(MyBean.class);
// use myBean ...

@PropertySource

@Configuration类可以使用@PropertySource注释将属性源贡献给环境对象:

@Configuration
@PropertySource("classpath:/com/acme/app.properties")
public class AppConfig { @Inject Environment env; @Bean
public MyBean myBean() {
return new MyBean(env.getProperty("bean.name"));
}
}

@ComponentScan

@Configuration类不仅可以使用组件扫描引导,还可以自己使用@ComponentScan注释配置组件扫描:

@Configuration
@ComponentScan("com.acme.app.services")
public class AppConfig {
// various @Bean definitions ...
}

@Import

@Configuration类可以使用@Import注释组合,因为@Configuration对象在容器中被管理为Spring bean,所以导入的配置可能会被注入——例如,通过构造函数注入:

@Configuration

   public class DatabaseConfig {

       @Bean
public DataSource dataSource() {
// instantiate, configure and return DataSource
}
} @Configuration
@Import(DatabaseConfig.class)
public class AppConfig { private final DatabaseConfig dataConfig; public AppConfig(DatabaseConfig dataConfig) {
this.dataConfig = dataConfig;
} @Bean
public MyBean myBean() {
// reference the dataSource() bean method
return new MyBean(dataConfig.dataSource());
}
}

@ContextConfiguration

组合多个@Configuration配置组件

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {AppConfig.class, DatabaseConfig.class})
public class MyTests { @Autowired MyBean myBean; @Autowired DataSource dataSource; @Test
public void test() {
// assertions against myBean ...
}
}

@Component

修饰一个类,表明一个注解的类是“部件”。@Configuration就是基于该注解。

@Autowired

自动注入,按照类型来匹配。

@Profile

分别开发环境和生产环境的配置

application.properties 配置文件

spring.profiles.active=development

测试环境

@Profile("development")
@Configuration
public class EmbeddedDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return embedded DataSource
}
}

生产环境

  @Profile("production")
@Configuration
public class ProductionDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return production DataSource
}
}

第二种方法

@Configuration
public class ProfileDatabaseConfig {
//测试环境
@Bean("dataSource")
@Profile("development")
public DataSource embeddedDatabase() { ... }
//生产环境
@Bean("dataSource")
@Profile("production")
public DataSource productionDatabase() { ... }
}

@ImportResource

导入Spring的配置文件,让配置文件里面的内容生效;Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件

  •     例:@ImportResource(locations = {"classpath:beans.xml"})
  •     但是SpringBoott推荐给容器中添加组件的方式;推荐使用全注解的方式

@EnableAutoConfiguration

启用Spring应用程序上下文的自动配置(开启自动配置spring),当你不需要加载某个自定义类时可以在yml文件中:spring.autoconfigure.exclude: XXXX 来排除某个类。

@ComponentScan

配置使用组件扫描指令与@ Configuration类。 提供与Spring XML的支持并行context:component-scan元件。它包含了annotation-config这个属性。

SpringBoot启动过程

从main中执行SpringApplication.run(GradleTest1Application.class, args);

@SpringBootApplication
public class GradleTest1Application { public static void main(String[] args) {
SpringApplication.run(GradleTest1Application.class, args);
} }

进入到SpirngApplication类中,然后在run方法中间接调用new SpringApplication的构造方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

SpringApplication(primarySources):源码开始


public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}

从这里开始,有SpringApplication构造方法中嵌套的源码

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader; //资源加载器,因为上一次调用赋值为null,所以之这里时null
Assert.notNull(primarySources, "PrimarySources must not be null"); //断言这里是否传入的SpringBoot的启动器是否为空
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //将SpringBoot启动器放入一个Set<Class<?>>中
/*WebApplicationType:Web应用程序可能类型的枚举,里面有三个枚举参数(查看Web是那种类型),deduceFromClasspath()方法中使用CLassUtils的class工具查看对应 的classpath路径下是否有对应的配置,如果有返回对应的Web类型
注意:源码在本代码块后第一个代码块中
*/
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/*加载默认配置类(ApplicationContextInitializer是初始化所有自动配置类的类),扫描所有自动配置jar包中“META-INF/spring.factories”的配置类jar包(
主要有org.springframework.boot.autoconfigure、org.springframework.boot.context、org.springframework.boot.beans)
找到实现ApplicationContextInitializer接口的类,并实例化他们
提示:源码在本代码块后第二个代码块中
*/
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//和上一行代码操作一致,只不过是在三个jar包中找到实现ApplicationListener.class类并实例化它们
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//找到主要的应用程序类
this.mainApplicationClass = deduceMainApplicationClass();
/**
deduceMainApplicationClass();源码说明:
private Class<?> deduceMainApplicationClass() {
try {
//直接new出一个运行异常,然后获得堆信息
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
//在堆信息中找到main方法
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
//加载main方法,并返回class对象
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
*/
}

this.webApplicationType = WebApplicationType.deduceFromClasspath();源码:

WebApplicationType:Web应用程序可能类型的枚举,里面有三个枚举参数(查看Web是那种类型),deduceFromClasspath()方法中使用CLassUtils的class工具查看对应 的classpath路径下是否有对应的配置,如果有返回对应的Web类型

/**
* An enumeration of possible types of web application.
*
* @author Andy Wilkinson
* @author Brian Clozel
* @since 2.0.0
*/
public enum WebApplicationType { /**
* The application should not run as a web application and should not start an
* embedded web server.
应用程序不应作为web应用程序运行,也不应该启动嵌入式web服务器。
*/
NONE, /**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet web服务器。
*/
SERVLET, /**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
应用程序应该作为一个反应性web应用程序运行,并启动一个嵌入式反应性web服务器。当前反应性web服务器在Spring5引进,它可以支持servlet容器,也可以支持servlet职位的容器
*/
REACTIVE; static WebApplicationType deduceFromClasspath() {
//判断类路径是否存在,存在:true 不存在:false
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
}

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 源码:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
} /**
重点!!!!!!!!!
这个类是返回在org.springframework.boot.autoconfigure、org.springframework.boot.context、org.springframework.boot.beans这三个包中继承type类型的所有类,
这个类在SpringApplication中经常使用!!!!!!!
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//获得类加载器
ClassLoader classLoader = getClassLoader(); //获得所有的自动配置类的路径信息
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
/* SpringFactoriesLoader.loadFactoryNames(type, classLoader)说明和源码
| | |
使用set集合为了确保惟一以防止重复,因为这里存放的是所有自动配置类(AutoConfiguration)
loadFactoryNames(type, classLoader));
参数说明: (type = ApplicationContextInitializer.class(记录被加载类的配置信息,并初始化) classLoader = 系统加载器) 方法调用了loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)的同名方法,
loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)源码: public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
//loadSpringFactories(classLoader):找到所有“META-INF/spring.factories”配置文件中的类
//getOrDefault(factoryTypeName, Collections.emptyList()); 过滤所有的类找出实现factoryTypeName的类并放入集合中 (factoryTypeName=ApplicationContextInitializer.class
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
在return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());中获得所有的自动配置类的路径信息
*/ //在获得所有需要初始化的自动配置类后,初始化他们
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//对类排序
AnnotationAwareOrderComparator.sort(instances);
//返回所有排序后的自动配置类集合
return instances;
}

到这里结束


run(args):源码开始


/**
* Run the Spring application, creating and refreshing a new
运行Spring应用程序,创建并刷新一个新应用程序
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//计时器(Spring创始人2001年写的)
StopWatch stopWatch = new StopWatch();
//启动计时器
stopWatch.start();
//ApplicationContext子类,封装了配置和生命周期,防止ApplicationContext客户端代码看到它们
ConfigurableApplicationContext context = null;
//存放SpringApplication启动运行时的异常
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//以服务器模式运行,在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式,激活该模式:System.setProperty("java.awt.headless", "true");
configureHeadlessProperty();
/*SpringApplication运行时的监视器,每运行一次都会创建一个SpringApplicationRunListener的子类放入SpringApplicationRunListeners类中的List<SpringApplicationRunListener> 的集合中*/
SpringApplicationRunListeners listeners = getRunListeners(args);
/** | | | 源码:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); //找到继承SpringApplicationRunListener类的所有类
}
*/ //开始启动所有继承SpringApplicationRunListener的监视器
listeners.starting();
try {
//加载默认参数,只是把args赋值给DefaultApplicationArguments类中的成员变量
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置环境,传入所有监听器和应用程序的参数
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//配置忽略一些Bean信息
configureIgnoreBeanInfo(environment);
//向控制台打印Banner(就是那个大的Spring字体,我们可以在classpath目录下创建一个banner.txt文件,把想要打印的东西放入进去就可以在程序启动时打印出来)
Banner printedBanner = printBanner(environment);
//创建应用上下文
context = createApplicationContext();
//获取Spring工厂的实例
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备上下文,并输出某些日志
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文,并输出某个日志
refreshContext(context);
//刷新上下问后置处理
afterRefresh(context, applicationArguments);
//计时停止了
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
} try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

SpringBoot深入理解的更多相关文章

  1. springBoot(1)---springboot初步理解

    springboot初步理解 在没有用SpringBoot之前,我们用spring和springMVC框架,但是你要做很多比如: (1)配置web.xml,加载spring和spring mvc 2) ...

  2. SpringBoot系列: 理解 Spring 的依赖注入(一)

    ==============================Spring 的依赖注入==============================对于 Spring 程序, Spring 框架为我们提供 ...

  3. SpringBoot简单理解

    SpringBoot 一.特点:1.默认大于配置,不需要大量配置文件,没有web.xml,拥有可运行的Application类. 2.一般通过java代码配置,而尽量少使用xml配置. 3.maven ...

  4. 【SpringBoot】 理解Spirng中的IOC原理

    前言 前文已经介绍了Spring Bean的生命周期,在这个周期内有一个重要的概念就是: IOC容器 大家也知道IOC是Sping 的重要核心之一,那么如何理解它呢,它又是产生什么作用呢?本文就IOC ...

  5. 【SpringBoot】 理解SpringBoot的启动原理

    前言 前文已经介绍了Spring Bean的生命周期,那么使用过程中发现SpringBoot 的启动非常快捷,本文将介绍SpringBoot的内部启动原理. 启动过程 如上图所示,我们先分析下Spri ...

  6. SpringBoot系列: 理解 Spring 的依赖注入(二)

    ==============================Spring 容器中 Bean 的名称==============================声明 bean 有两个方式, 一个是 @B ...

  7. SpringBoot对比SpringMVC,SpringMVC 处理请求过程

    (问较多:1.SpringBoot对比SpringMVC.2.SpringMVC 处理请求过程.问:springboot的理解 Spring,Spring MVC,Spring Boot 三者比较 S ...

  8. SSM(SpringMVC+Spring+Mybatis)框架学习理解

    近期做到的项目中,用到的框架是SSM(SpringMVC+Spring+Mybatis).之前比较常见的是SSH.用到了自然得了解各部分的分工 spring mvc 是spring 处理web层请求的 ...

  9. SpringBoot | 第零章:前言

    缘起 前段时间公司领导叫编写一两课关于springboot的基础知识培训课程,说实话,也是今年年初才开始接触了SpringBoot这个脚手架,使用了之后才发现打开了一个新世界.再之后也没有一些系统的学 ...

随机推荐

  1. Python将word文档转换成PDF文件

    如题. 代码: ''' #將word文档转换为pdf文件 #用到的库是pywin32 #思路上是调用了windows和office功能 ''' #导入所需库 from win32com.client ...

  2. NET 5 使用HttpClient和HttpWebRequest

    HttpWebRequest 这是.NET创建者最初开发用于使用HTTP请求的标准类.HttpWebRequest是老版本.net下常用的,较为底层且复杂,访问速度及并发也不甚理想,但是使用HttpW ...

  3. C#常用的算法

    一.二分法 注:一定是有序的数组,才可以使用这种算法,如果数组没有排序则先进行排序后再调用此方法. 二分顾名思义,就是将一组数据对半分开(比如左右两部分,下面用左右数组表示),从中间位置开始查找, 如 ...

  4. SQL语句实现增删改查

    查询语句SELECT *FROM mydriect WHERE id=1; 删除语句DELETE FROM mydriect WHERE id=1; 修改语句UPDATE mydriect SET 自 ...

  5. 如何解决Visual Studio 首次调试 docker 的 vs2017u5 exists, deleting Opening stream failed, trying again with proxy settings

    前言 因为之前我电脑安装的是windows10家庭版,然而windows10家庭没有Hyper-v功能. 搜索了几篇windows10家庭版安装docker相关的博客,了解一些前辈们走过的坑. 很多人 ...

  6. Beta冲刺——第五天

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...

  7. kafka-spark偏移量提交至redis kafka1.0版本

    kafka版本 1.0.0 spark版本 spark-streaming-kafka-0-10_2.11/** * @created by imp ON 2019/12/21 */class Kaf ...

  8. centos7搭建sonarqube环境+jenkins部署全流程

    一.简介sonarqube是一个用于代码质量管理的开源平台,用于管理源代码的质量 不遵循代码标准sonar可以通过PMD,CheckStyle,Findbugs等等代码规则检测工具规范代码编写.潜在的 ...

  9. 使用lua+redis解决发多张券的并发问题

    前言 公司有一个发券的接口有并发安全问题,下面列出这个问题和解决这个问题的方式. 业务描述 这个接口的作用是给会员发多张券码.涉及到4张主体,分别是:用户,券,券码,用户领取记录. 下面是改造前的伪代 ...

  10. 在md里画流程图

    可以使用名为mermaid的代码块,即 ```mermaid``` 需要md解析器能解析mermaid mermaid使用详情参见