Springboot的优点
- 内置servlet容器,不需要在服务器部署 tomcat。只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目
- SpringBoot提供了starter,把常用库聚合在一起,简化复杂的环境配置,快速搭建spring应用环境
- 可以快速创建独立运行的spring项目,集成主流框架
- 准生产环境的运行应用监控
SpringBoot 中的 starter 到底是什么 ?
starter提供了一个自动化配置类,一般命名为 XXXAutoConfiguration ,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。
运行 SpringBoot 有哪几种方式?
- 打包用命令或者者放到容器中运行
- 用 Maven/Gradle 插件运行
- 直接执行 main 方法运行
SpringBoot 常用的 Starter 有哪些?
- spring-boot-starter-web :提供 Spring MVC + 内嵌的 Tomcat 。
- spring-boot-starter-data-jpa :提供 Spring JPA + Hibernate 。
- spring-boot-starter-data-Redis :提供 Redis 。
- mybatis-spring-boot-starter :提供 MyBatis 。
springboot的run方法
在Spring Boot中,应用程序的入口点是一个名为SpringApplication的类。该类提供了一个静态的run()方法,可以使用该方法启动Spring Boot应用程序。
run方法接受两个参数:
primarySource:指定Spring应用程序的主要源代码。通常情况下,这是包含main()方法的类。args:传递给Spring应用程序的命令行参数。
在Spring Boot中,run()方法的作用是创建Spring应用程序的上下文,并启动Spring应用程序。在创建应用程序上下文时,Spring Boot会自动扫描并加载应用程序中的所有组件和配置,并将它们装载到应用程序上下文中。然后,Spring Boot会启动嵌入式Web服务器,并将应用程序的HTTP端口绑定到服务器上,使得应用程序可以接收HTTP请求。
在run()方法返回后,应用程序将进入运行状态,并等待来自客户端的HTTP请求。当收到HTTP请求时,嵌入式Web服务器将调用应用程序的控制器方法来处理请求,并返回相应的HTTP响应。
实现自动配置
- 编写自动配置类:自动配置类需要标注
@Configuration和@ConditionalOnXxx注解,其中@ConditionalOnXxx注解用于定义自动配置的条件,例如@ConditionalOnClass表示当classpath中存在某个类时才会自动配置相应的组件。自动配置类中需要编写@Bean方法来配置相应的组件。 - 将自动配置类打包成jar包:将自动配置类打包成一个独立的jar包,同时在
META-INF/spring.factories文件中指定自动配置类的全限定名。 - 引入依赖:在使用自动配置功能的项目中引入自动配置的jar包。
- 配置项目:在项目中配置相应的参数,例如在application.properties文件中设置数据库的连接信息。
- 运行项目:启动项目并测试自动配置是否生效。
需要注意的是,在编写自动配置类时应尽量遵循约定大于配置的原则,即通过类路径、环境变量、配置文件等条件来自动配置组件,从而使得开发者可以更加专注于业务逻辑的实现,而不必关心底层的组件实现细节。
另外,Spring Boot的自动配置原理是基于Spring框架的扩展机制实现的,因此在编写自动配置类时需要了解Spring框架的基本原理和扩展机制。
springboot自动配置原理
Spring Boot的自动配置原理可以概括为“条件化配置”,即根据一定的条件自动配置相应的组件。具体来说,它包括以下几个步骤:
- 扫描类路径:Spring Boot会扫描项目中所有的类,查找标注有特定注解的类,这些注解包括
@Configuration、@EnableAutoConfiguration和@ConditionalOnXxx等。 - 加载配置类:当Spring Boot找到标注有
@Configuration的类时,会将它们加载到Spring容器中,这些类中可以包含@Bean注解的方法,用于配置Bean。 - 自动配置:当Spring Boot找到标注有
@EnableAutoConfiguration的类时,会根据classpath中的依赖关系自动配置相应的组件,这些组件包括WebMvc、JPA、JdbcTemplate等,每个组件都对应一个或多个条件,只有当这些条件满足时才会自动配置相应的组件。 - 条件化配置:在自动配置时,Spring Boot会根据类路径、环境变量、配置文件等条件来判断是否需要自动配置某个组件,这些条件可以通过
@ConditionalOnXxx注解来定义,例如@ConditionalOnClass表示当classpath中存在某个类时才会自动配置相应的组件。
总的来说,Spring Boot的自动配置原理是基于条件化配置的,根据一定的条件来自动配置相应的组件,从而简化开发者的工作。开发者也可以通过定义自己的条件化配置来扩展Spring Boot的自动配置功能。
springboot启动流程
- 加载Spring Boot配置:Spring Boot在启动时会扫描项目中的application.properties或application.yml等配置文件,并将其中的配置加载到Spring环境中。
- 加载Spring相关配置:Spring Boot会根据classpath中的配置文件自动配置Spring容器,包括加载bean、自动配置组件等。
- 执行自定义的初始化代码:Spring Boot允许开发者在启动过程中执行自定义的初始化代码,例如初始化数据库连接池、加载数据等。
- 启动Web容器:Spring Boot会根据classpath中的依赖自动选择合适的Web容器(如Tomcat、Jetty等),并将应用程序部署到Web容器中。
- 执行应用程序:当Web容器启动并准备就绪后,Spring Boot会自动执行应用程序的代码,并将响应结果返回给客户端。
总的来说,Spring Boot启动过程主要包括配置加载、容器初始化、应用程序执行和Web容器启动等步骤,这些步骤通过自动配置和约定大于配置的方式来简化开发者的工作,使得开发者可以更加专注于业务逻辑的实现。
Spring Boot 的核心注解是哪个?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
- @ComponentScan:Spring组件扫描。
如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?
- Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过
java -jar xxx.jar命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。 - Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在
\BOOT-INF\classes目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。
SpringBoot的starter实现原理是什么?
原理就是因为在@EnableAutoConfiguration注解,会自动的扫描jar包下的META-INF/spring.factories文件的配置类,写在这里面的类都是需要被自动加载的
将configuration类中定义的bean加入spring到容器中。就相当于加载之前我们自己配置组件的xml文件。而现在SpringBoot自己定义了一个默认的值,然后直接加载进入了Spring容器。
SpringBoot提供的自动配置依赖模块都以spring-boot-starter-为命名前缀。 所有的spring-boot-starter都有约定俗成的默认配置,但允许调整这些配置默认的行为。
spring 和springboot的区别
Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,为更快,更高效的开发生态系统铺平了道路。
Spring Boot中的一些特征:
- 创建独立的Spring应用。
- 嵌入式Tomcat、Jetty、 Undertow容器(无需部署war文件)。
- 提供的starters 简化构建配置
- 尽可能自动配置spring应用。
- 提供生产指标,例如指标、健壮检查和外部化配置
- 完全没有代码生成和XML配置要求
Maven依赖不同:springboot在构建期间可以将其他依赖项自动加入到项目中。 比如我们通常使用Spring Test,JUnit,Hamcrest和Mockito库。在Spring项目中,我们应该将所有这些库添加为依赖项。但是在Spring Boot中,我们只需要添加spring-boot-starter-test依赖项来自动包含这些库。
spring在运行前需要使用xml文件做很多配置,而springboot帮我们实现了这些配置的自动加载,基于注解和简单的yml配置即可
spring的web程序还是打包为war然后再Tomcat里运行,而springboot内嵌了Tomcat直接打成可运行的jar
Spring Boot 可执行 Jar 包运行原理
将 Spring Boot 应用打成可执行 Jar包很容易,只需要在 pom 中加上一个 Spring Boot 提供的插件,然后在执行mvn package即可
运行完mvn package后,我们会在 target 目录下看到两个 jar 文件。myproject-0.0.1-SNAPSHOT.jar 和 myproject-0.0.1-SNAPSHOT.jar.original。第一个 jar 文件就是我们应用的可执行 jar 包,第二个 Jar 文件是应用原始的 jar 包。
- Spring Boot 可执行 Jar 包的入口点是 JarLauncher 的 main 方法;
- 这个方法的执行逻辑是先创建一个 LaunchedURLClassLoader,这个加载器加载类的逻辑是:先判断根类加载器和扩展类加载器能否加载到某个类,如果都加载不到就从 Boot-INF 下面的 class 和 lib 目录下去加载;
- 读取Start-Class属性,通过反射机制调用启动类的 main 方法,这样就顺利调用到我们开发的 Spring Boot 主启动类的 main 方法了。
spring里面有哪些设计模式
1、简单工厂模式:BeanFactory就是简单工厂模式的体现,根据传入一个唯一标识来获得 Bean 对象。
2、工厂方法模式:FactoryBean就是典型的工厂方法模式。spring在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法。每个 Bean 都会对应一个 FactoryBean,如 SqlSessionFactory 对应 SqlSessionFactoryBean。
3、单例模式:一个类仅有一个实例,提供一个访问它的全局访问点。Spring 创建 Bean 实例默认是单例的。
4、适配器模式:SpringMVC中的适配器HandlerAdatper。由于应用会有多个Controller实现,如果需要直接调用Controller方法,那么需要先判断是由哪一个Controller处理请求,然后调用相应的方法。当增加新的 Controller,需要修改原来的逻辑,违反了开闭原则(对修改关闭,对扩展开放)。
为此,Spring提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter 实现类,当请求过来,SpringMVC会调用getHandler()获取相应的Controller,然后获取该Controller对应的 HandlerAdapter,最后调用HandlerAdapter的handle()方法处理请求,实际上调用的是Controller的handleRequest()。每次添加新的 Controller 时,只需要增加一个适配器类就可以,无需修改原有的逻辑。
常用的处理器适配器:SimpleControllerHandlerAdapter,HttpRequestHandlerAdapter,AnnotationMethodHandlerAdapter。
5、代理模式:spring 的 aop 使用了动态代理,有两种方式JdkDynamicAopProxy和Cglib2AopProxy。
6、观察者模式:spring 中 observer 模式常用的地方是 listener 的实现,如ApplicationListener。
7、模板模式: Spring 中 jdbcTemplate、hibernateTemplate 等,就使用到了模板模式。
垃圾收集器有哪些?
Serial 收集器
单线程收集器,使用一个垃圾收集线程去进行垃圾回收,在进行垃圾回收的时候必须暂停其他所有的工作线程( Stop The World ),直到它收集结束。
特点:简单高效;内存消耗小;没有线程交互的开销,单线程收集效率高;需暂停所有的工作线程,用户体验不好。
ParNew 收集器
Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其他行为、参数与 Serial 收集器基本一致。
Parallel Scavenge 收集器
新生代收集器,基于复制清除算法实现的收集器。特点是吞吐量优先,能够并行收集的多线程收集器,允许多个垃圾回收线程同时运行,降低垃圾收集时间,提高吞吐量。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值(吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间))。Parallel Scavenge 收集器关注点是吞吐量,高效率的利用 CPU 资源。CMS 垃圾收集器关注点更多的是用户线程的停顿时间。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
-XX:MaxGCPauseMillis参数的值是一个大于0的毫秒数,收集器将尽量保证内存回收花费的时间不超过用户设定值。-XX:GCTimeRatio参数的值大于0小于100,即垃圾收集时间占总时间的比率,相当于吞吐量的倒数。
Serial Old 收集器
Serial 收集器的老年代版本,单线程收集器,使用标记整理算法。
Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本。多线程垃圾收集,使用标记整理算法。
CMS 收集器
Concurrent Mark Sweep ,并发标记清除,追求获取最短停顿时间,实现了让垃圾收集线程与用户线程基本上同时工作。
CMS 垃圾回收基于标记清除算法实现,整个过程分为四个步骤:
- 初始标记: 暂停所有用户线程(
Stop The World),记录直接与GC Roots直接相连的对象 。 - 并发标记:从
GC Roots开始对堆中对象进行可达性分析,找出存活对象,耗时较长,但是不需要停顿用户线程。 - 重新标记: 在并发标记期间对象的引用关系可能会变化,需要重新进行标记。此阶段也会暂停所有用户线程。
- 并发清除:清除标记对象,这个阶段也是可以与用户线程同时并发的。
在整个过程中,耗时最长的是并发标记和并发清除阶段,这两个阶段垃圾收集线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点:并发收集,停顿时间短。
缺点:
- 标记清除算法导致收集结束有大量空间碎片。
- 产生浮动垃圾,在并发清理阶段用户线程还在运行,会不断有新的垃圾产生,这一部分垃圾出现在标记过程之后,
CMS无法在当次收集中回收它们,只好等到下一次垃圾回收再处理;
G1收集器
G1垃圾收集器的目标是在不同应用场景中追求高吞吐量和低停顿之间的最佳平衡。
G1将整个堆分成相同大小的分区(Region),有四种不同类型的分区:Eden、Survivor、Old和Humongous。分区的大小取值范围为 1M 到 32M,都是2的幂次方。分区大小可以通过-XX:G1HeapRegionSize参数指定。Humongous区域用于存储大对象。G1规定只要大小超过了一个分区容量一半的对象就认为是大对象。
G1 收集器对各个分区回收所获得的空间大小和回收所需时间的经验值进行排序,得到一个优先级列表,每次根据用户设置的最大回收停顿时间,优先回收价值最大的分区。
特点:可以由用户指定期望的垃圾收集停顿时间。
G1 收集器的回收过程分为以下几个步骤:
- 初始标记。暂停所有其他线程,记录直接与
GC Roots直接相连的对象,耗时较短 。 - 并发标记。从
GC Roots开始对堆中对象进行可达性分析,找出要回收的对象,耗时较长,不过可以和用户程序并发执行。 - 最终标记。需对其他线程做短暂的暂停,用于处理并发标记阶段对象引用出现变动的区域。
- 筛选回收。对各个分区的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,然后把决定回收的分区的存活对象复制到空的分区中,再清理掉整个旧的分区的全部空间。这里的操作涉及存活对象的移动,会暂停用户线程,由多条收集器线程并行完成。
线程和协程
1、线程是操作系统的资源,线程的创建、切换、停止等操作都非常的消耗资源,而协程的创建不需要调用系统的功能,编程语言自身就能完成
2、线程在多核环境下能够做到真正意义上的并行,而协程是为并发产生的
3、一个具有多个线程的程序可以同时运行多个线程,而协同程序需要彼此协作的运行
4、线程属于同步机制,而协同是异步
5、线程是抢占式,而协程是非抢占式,所以需要用户自己释放使用权来切换到其它协程,因此同一时间只有一个协程具有运行权
6、操作系统能够创建数量为千的线程,但是能够创建上万的协程
io多路复用
IO多路复用指的是单个进程或者线程能同时处理多个IO请求,select,epoll,poll是LinuxAPI提供的复用方式。本质上由操作系统内核缓冲IO数据,使得单个进程线程能监视多个文件描述符。select是将装有文件描述符的集合从用户空间拷贝到内核空间,底层是数组,poll和select差距不大,但是底层是链表,这就代表没有上限,而select有数量限制。epoll则是回调的形式,底层是红黑树,避免轮询,时间复杂度从O(n)变为O(1)
类加载机制
类加载的过程中首先判断这个是否被加载过,如果没有被加载过,那么调用类加载器进行加载,判读这个类是否符合规范,如果不符合就抛出异常,加载成功就会生成class对象。 接下来是链接过程,分为三步:准备,验证,准备,解析。 验证:确保文件符合规范,不会危害虚拟机自身的安全,对文件格式,字节码,元数据,符号引用进行验证。 准备:为类变量分配初始空间以及默认初始值,即零值。这里不会为实例变量分配,类变量分配在方法区中,实例变量跟随对象分配在堆中,final修饰的在编译期间就分配了,在准备阶段会显式的初始化。 解析:将常量池内的直接引用转为直接引用的过程。 链接过程完成之后开始初始化的过程: 初始化阶段就是执行类构造器方法的过程。此方法不需要定义,一个类只会被加载一次,虚拟机必须保证在多线程条件下类的构造方法是被加锁的。
抽象类和接口
1.抽象类多用于在同类事物中有无法具体描述的方法的场景,而接口多用于不同类之间,定义不同类之间的通信规则。2.接口只有定义,而抽象类可以有定义和实现。3.接口需要实现implement,抽象类只能被继承extends,一个类可以实现多个接口,但一个类只能继承一个抽象类。4.抽象类倾向于充当公共类的角色,当功能需要累积时,用抽象类;接口被运用于实现比较常用的功能,功能不需要累积时,用接口。
springboot的常用注解
@SpringBootApplication:它是SpringBoot的核心注解,用于开启自动配置,准确的说是通过该注解内的@EnablAutoConfiguration注解实现的自动配置。
@EnableAutoConfiguration:自动配置注解,在启动Spring应用程序上下文时进行自动配置,自动配置通常是基于项目classpath中引入的类和已定义的bean来实现的。
@Configuration:配置类注解,根据一些特定条件来控制bean的实例化的行为。
@ComponentScan:位置在SpringBoot的启动类上,Spring包扫描。
SpringMVC 的执行流程如下。
- 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
- 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
- DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
- HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
- Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
- HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
- DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
- ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
- DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
- 视图负责将结果显示到浏览器(客户端)。
Nginx怎么负载均衡的
如何实现负载均衡就要从这个正向代理和反向代理说起,正向代理最大的特点是,客户端非常明确要访问的服务器的地址,它代理客户端,替客户端发出请求。客户端把请求发给代理服务器,由代理服务器代替它请求网站,最终再将请求相应返回给客户端。这就是正态代理的过程,这个过程服务器不知道真正发出请求的是谁。反向代理的话,如果请求量大幅度增长的话,服务器可能自己应付不过来,需要其他服务器的帮忙,来自其他客户端的请求都发送到了这个代理服务器上,再由代理服务器按照一定的规则向各个服务器分发请求。反向代理隐藏了自己的服务器的信息,它代理的是服务端,代其接收请求。换句话说,反向代理的过程中,客户端并不知道具体时间那台服务器处理了自己的请求,如此一来,提高了访问速度,也提供了安全保证。
这个分发请求有几种算法:轮训算法,就是遍历服务器节点列表,按照次序每次选择一台处理请求,当所有的都被调用过一次之后,再从第一个节点重新遍历。还有加权轮训,每个服务器会有自己的weight,一般情况下weight越大的服务器意味着它的性能越好,可以承载更多的请求。客户端的请求按照这个权值进行分配,优先给权值最大的服务器进行分配。还有IP哈希算法,对发出请求的客户端IP的hash值进行分配服务器,该算法可以保证同一个IP发出的请求映射到同一个服务器,解决了集群部署环境下session不共享的问题。还有一些对url进行hash的算法,当有缓存的时候效率比较高,但是nginx默认不支持这种算法,需要依赖第三方的库。