什么是Spring框架?
框架,容器,生态
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
Spring有两个核心部分:IOC和Aop
IOC:控制反转,把创建对象的工程交给Spring进行管理
Aop:面向切面,不修改源代码进行功能增强
Spring特点:
(1)方便解耦,简化开发(2)Aop编程支持(3)方便程序测试
(4)方便和其他框架进行整合(5)方便进行事务操作(6)降低API开发难度
Spring,Spring MVC,Spring Boot 什么关系?
Spring (基石)包含了多个功能模块(上面刚刚提高过),其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。
Spring MVC 是 Spring 中的一个很重要的模块,主要处理web开发的路径映射和视图渲染。主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
Spring Boot 旨在简化 Spring 开发(减少配置文件),做到起步依赖、自动配置、端点监控。
1、快速整合第三方框架,比如redis,mybatis等等
2、全部采用注解方式,没有繁琐的xml配置。
3、内置http服务器,比如jetty,tomcat。不需要额外的去集成下载tomcat。
springcloud 和springboot的区别
SpringBoot:是一个快速开发框架,通过用MAVEN依赖的继承方式,可以快速整合第三方常用框架,完全采用注解化(使用注解方式启动SpringMVC),简化XML配置,内置HTTP服务器(Tomcat,Jetty),最终以Java应用程序进行执行。
spring_cloud是基于spring_boot的微服务分布式架构,它将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,形成各种springboot微服务,然后这些微服务共同组成了springcloud微服务架构。(包括服务注册中心eureka、配置中心Springcloud config、消息总线bus、负载均衡ribbon、断路器hystrix、路由网关zuul、数据监控等。)
spring cloud把各个微服务集中到一起,然后分开管理,各个微服务专注于自己的业务。每个微服务对应的就是一个spring boot项目。
通过SpringBoot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
IOC和AOP
IOC:控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理。使用IOC目的:降低耦合度
IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
AOP:面向切面,不通过修改源代码方式,在主干功能里面添加新功能。
能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP和 AspectJ AOP什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。 AspectJ不是Spring组成部分,是一个独立的AOP框架。 Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单, 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
@Component 和 @Bean 的区别是什么?
1.@Component 注解作⽤于类,⽽ @Bean 注解作⽤于⽅法
2.@Component 通常是通过类路径扫描来⾃动装配到Spring容器中; @Bean 注解告诉Spring容器,你可以从下面这个方法中拿到一个Bean
3.第三⽅库中的类需要装配到 Spring 容器时,则只能通过 @Bean 来实现。
@Autowired 与@Resource的区别
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
共同点:两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
不同点:
@Autowired只根据类型注入,@Resource可以根据类型,也可以根据名称。都没有指定默认根据名称。
@Resource 由jdk提供@Autowired 由spring提供。使用@Resource可以减少代码和Spring之间的耦合。
Spring容器中的Bean是否线程安全?
Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。
如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。实际上大部分时间Bean是无状态的(比如Dao) 所以说在某种程度上来说Bean其实是安全的。
如果Bean是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域 把 "singleton"改为’‘protopyte’ 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的安全了。
-
无状态就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。
-
有状态就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。
controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
@Controller @Service是不是线程安全的?
默认配置下不是的。为啥呢?因为默认情况下@Controller没有加上@Scope,没有加@Scope就是默认值singleton,单例的。意思就是系统只会初始化一次Controller容器,所以每次请求的都是同一个Controller容器,当然是线程不安全的。
1.尽量不要在@Controller/@Service等容器中定义静态变量,不论是单例(singleton)还是多实例(prototype)都是线程不安全的。
2.默认注入的Bean对象,在不设置scope的时候他也是线程不安全的。
3.一定要定义变量的话,用ThreadLocal来封装,这个是线程安全的
4.在单例模式下Controller中只有用ThreadLocal封装的变量是线程安全的
Spring 框架中⽤到了哪些设计模式
⼯⼚设计模式 : Spring使⽤⼯⼚模式通过 BeanFactory 、 ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
包装器设计模式 : 我们的项⽬需要连接多个数据库,⽽且不同的客户在每次访问中根据需要 会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 观察者模式: Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、spring MVC 中也是⽤ 到了适配器模式适配 Controller 。
Spring动态代理
代理模式 当访问对象不适合或者不能直接引用目标对象时,需要给访问对象提供一个代理以控制对该对象的访问,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用; 代理对象可以扩展目标对象的功能; 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
其主要缺点是:
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢; 增加了系统的复杂度;
动态代理分为两大类:基于接口的动态代理和基于类的动态代理
-
基于接口:JDK动态代理
-
基于类:CGLIB
JDK动态代理
Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法
Spring事务是怎么实现的?
1.Spring事务底层是基于数据库事务和AOP机制的 2.首先对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean 3.当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解 4.如果加了,那么则利用事务管理器创建一个数据库连接 5.并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交,这是实现Spring事务非常重要的一步 6.然后执行当前方法,方法中会执行sql 7.执行完当前方法后,如果没有出现异常就直接提交事务 8.如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚。 如果一个方法抛出Exception或者Checked异常(编译时异常),Spring事务管理默认不进行回滚。
Spring MVC的简单原理
浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理。 前端控制器请求HandlerMapping查找Handler(可以根据xml配置、注解进行查找),调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。 前端控制器会调用处理器适配器HandlerHandlerAdapter去执行Handler。Handler执行完成给适配器返回ModelAndView,处理器适配器向前端控制器返回ModelAndView。 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名解析成真正的视图。(通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可) 视图解析器向前端控制器返回View,前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域),并将渲染结果返回给客户端
spring bean的加载过程
1.spring读取配置或注解
先通过扫描指定包路径下的spring注解,比如@Component、@Service等spring识别的注解或者是xml配置的属性(通过读取流解析成Document,Document)然后spring会解析这些属性,将这些属性封装到BeanDefintaion这个接口的实现类中。
具体采用的是BeanDefinitionParser接口中的parse()方法,该接口有很多不同的实现类。通过实现类去解析注解或者xml然后放到BeanDefination中,BeanDefintaion的作用是(配置元信息)集成了我们的配置对象中的各种属性,重要的有这个bean的ClassName,还有是否是Singleton、对象的属性和值等(如果是单例的话,后面会将这个单例对象放入到spring的单例池中)。spring后期如果需要这些属性就会直接从BeanDefintaion中获取。
然后,再注册到一个ConcurrentHashMap中,在spring中具体的方法就是registerBeanDefinition(),这个Map存的key是对象的名字,比如Person这个对象,它的名字就是person,值是BeanDefination,它位于DefaultListableBeanFactory类下面的beanDefinitionMap类属性中,同时将所有的bean的名字放入到beanDefinitionNames这个list中,目的就是方便取beanName。
2.spring的bean的生命周期
实例化:BeanFactory会调用createBeanInstance()方法,该阶段主要是从beanDefinitionMap循环读取bean,获取它的属性,然后利用反射读取对象的构造方法(spring会自动判断是否是有参数还是无参数,以及构造方法中的参数是否可用),然后再去创建实例。
初始化:属性赋值 PopulateBean()会对bean的依赖属性进行填充,@AutoWired注解注入的属性就发生这个阶段,假如我们的bean有很多依赖的对象,那么spring会依次调用这些依赖的对象进行实例化。
初始化的过程还包括将初始化好的bean放入到spring的缓存中、填充我们预设的属性进一步做后置处理等。
使用和销毁
在Spring将所有的bean都初始化好之后,我们的业务系统就可以调用了。而销毁主要的操作是销毁bean,主要是伴随着spring容器的关闭,此时会将spring的bean移除容器之中
3.spring的BeanPostProcessor处理器
spring的另一个强大之处就是允许开发者自定义扩展bean的初始化过程,最主要的实现思路就是通过BeanPostProcessor后置处理器来实现的。这些处理器渗透在bean创建的前前后后,穿插在spring生命周期的各个阶段,每一步都会影响着spring的bean加载过程。
实例化之前,会调用BeanPostProcessor的InstantiationAwarepostProcessBeforeInstantiation方法。这个阶段允许在Bean进行实例化之前,允许开发者自定义逻辑,如返回一个代理对象。不过需要注意的是假如在这个阶段返回了一个不为null的实例,spring就会中断后续的过程。
BeanPostProcessor.postProcessAfterInstantiation();这个阶段是Bean实例化完毕后执行的后处理操作,所有在初始化逻辑、装配逻辑之前执行。
BeanPostProcessor.postProcessBeforeInitialization该方法在bean初始化方法前被调用,Spring AOP的底层处理也是通过实现BeanPostProcessor来执行代理逻辑的。
BeanPostProcessor.postProcessAfterInitialization可以在这个方法中进行bean的实例化之后的处理,比如我们的自定义注解,对依赖对象的版本控制自动路由切换。比如有一个服务依赖了两种版本的实现,我们如何实现自动切换呢?这时候可以自定义一个路由注解,假如叫@RouteAnnotaion,然后实现BeanPostProcessor接口,在其中通过反射拿到自定义的注解@RouteAnnotaion再进行路由规则的设定。
容器启动运行阶段 SmartLifecycle.start
容器正式渲染完毕,开始启动阶段,bean已经在spring容器的管理下,程序可以随时调用
容器停止销毁
SmartLifecycle.stop(Runnable callback) spring容器停止运行 DisposableBean.destroy() spring会将所有的bean销毁,实现的bean实例被销毁的时候释放资源
Spring延迟加载
延迟初始化通常又被称为“懒加载”。在启动时不初始化Bean,直到用到这个Bean的时候才去初始化。
默认情况下,Bean在启动时进行初始化。通过加@Lazy注解实现。大对象,很少用的对象,可以使用懒加载
普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥第⼀次进⾏context.getBean() 时进⾏触发。
Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进⾏初始化并依赖注⼊。
延迟失效
Spring框架延迟加载属性在调用getBean之后将会失效,因为getBean方法是初始化bean的入口。
-
非延迟加载的类中不能自动注入延迟加载的类,会导致延迟加载失效;
-
如果想要实现某个类延迟加载使用自动注入功能时需要调用链前都不存在非延迟加载类,否则延迟加载失效。
Spring循环依赖
多个bean之间相互依赖,形成了一个闭环。 比如:A中一个属性引用了B,B中有一个属性引用了A。实例化A的时候,要去属性赋值B,找到B,B又进行实例化,需要A赋值,产生了死循环。
如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。先new两个对象,再调用属性。但是,在 Spring 中循环依赖就是一个问题了,因为,在 Spring 中,一个对象并不是简单 new 出来了,而是会经过一系列的 Bean 的生命周期,就是因为 Bean 的生命周期所以才会出现循环依赖问题。
通过加缓存的方式解决,需要提前暴露半成品bean。
Spring中Bean的生命周期
Spring 扫描 class 反射后封装成beanDefinition对象; 放入beanDefinitionMap 遍历map; 验证(是否单例、是否延迟加载、是否抽象)->推断构造方法->准备开始进行实例 填充原始对象中的属性(依赖注入); 如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象; 把最终生成的代理对象放入(一级缓存)单例池中,下次 getBean 时就直接从单例池拿即可;
Spring中单例Bean的三级缓存
三个HashMap
一级缓存:用于存放完全初始化好的bean
二级缓存:存放原始的bean对象(仅完成实例化,但还未进行属性注入和初始化),用于解决循环依赖
三级缓存:存放bean工厂对象(延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存。),用于解决循环依赖
二级缓存作用:
实例化后,还没进行属性填充时,把A对象的引用放入二级缓存,然后进行属性填充,需要填充B。B进行实例化,把自己的原始bean对象放入二级缓存。B进行填充,发现一级缓存中没有,去二级缓存中找,可以找到,B就填充完成。B把自己放入一级缓存,删掉二级缓存中的引用。回到A,此时一级缓存已经有B了,A也就可以完成属性的创建了。
对于 B 而言,就算在属性注入时,注入的是 A 原始对象,也没有关系,因为调的是一个引用,后面会完整。
三级缓存的作用:
如果仅仅只是为了破解循环依赖,二级缓存够了,压根就不必要三级。
考虑到动态代理机制,Spring代理对象的产生是在填充属性后才进行的,原理是通过后置处理器BeanPostProcessor来实现。如果只用二级缓存,为了解决代理问题,暴露出来的bean需要是代理后的,所以就要在属性填充的时候就生成代理对象,与spring的对象生命周期相违背。
三级缓存主要针对的就是动态代理类型的对象。
三级缓存中工厂中有一个getObject()方法,会判断对象是否需要代理,如果否则直接返回,如果是则返回代理对象。
Spring 先在一个三级缓存放置一个工厂,如果产生循环依赖,那么就调用这个工厂提早得到代理对象。如果没产生依赖,这个工厂根本不会被调用,所以 Bean 的生命周期就是对的。通过三级缓存来延迟生成代理对象,这样只有在循环依赖这种情况下不得不用到bean的时候才生成代理bean并返回。
三个缓存如何配合
-
首先,获取单例 Bean 的时候会通过 BeanName 先去 singletonObjects(一级缓存) 查找完整的 Bean,如果找到则直接返回,否则进行步骤 2。
-
看对应的 Bean 是否在创建中,如果不在直接返回找不到,如果是,则会去 earlySingletonObjects (二级缓存)查找 Bean,如果找到则返回,否则进行步骤 3
-
去 singletonFactories (三级缓存)通过 BeanName 查找到对应的工厂,如果存着工厂则通过工厂创建 Bean ,并且放置到 earlySingletonObjects 中。
-
如果三个缓存都没找到,则返回 null。
为什么必须单例
如果两个 Bean 都是原型模式的话。那么创建 A1 需要创建一个 B1。创建 B1 的时候要创建一个 A2。
创建 A2 又要创建一个 B2。创建 B2 又要创建一个 A3。创建 A3 又要创建一个 B3.....就无限卡下去了。
为什么不能全是构造器注入
如果全是构造器注入,比如A(B b)
,那表明在 new 的时候,就需要得到 B,此时需要 new B 。
但是 B 也是要在构造的时候注入 A ,即B(A a)
,这时候 B 需要在一个 map 中找到不完整的 A ,发现找不到。
为什么找不到?因为 A 没办法实例化完成。因此如果全是构造器注入的话,那么 Spring 无法处理循环依赖。
Bean放入三级缓存的时机是在Bean实例化之后调用的,如果是构造器注入,则该bean无法通过构造器完成实例化操作
Spring Boot 配置文件加载顺序
依次为: bootstrap.properties -> bootstrap.yml -> application.properties -> application.yml
其中 bootstrap.properties 配置为最高优先级
先加载的会被后加载的覆盖掉,所以.properties和.yml同时存在时,.properties会失效,.yml会起作用。
-
bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。
-
bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。
Springboot自动配置原理
SpringBoot的自动配置就是当spring容器启动后,一些自动配置类就自动装配到IOC容器中,不需要我们手动去注入,从而简化了开发,省去了繁琐的配置。
SpringBoot想要运行需要有一个主程序类,是被@SpringBootApplication标识的,自动装配就跟这个注解有关。
从该注解点进去源码,发现是三个复合注解。
@SpringBootConfiguration:底层还是一个@Configuration,代表当前是一个配置类。@ComponentScan:指定组件扫描的类型和范围,默认主程序所在的包和子包。
@EnableAutoConfiguration:开启自动配置,这个注解是自动配置的重点
1.进入@EnableAutoConfiguration,有一个@AutoConfigurationPackage自动装配包,将指定的一个包下的所有组件导入到容器当中。
在@AutoConfigurationPackage 注解中存在一个 @Import({Registrar.class}) 注解,自动配置包就是通过这个 Registrar 类的方法来完成的。
在Registrar中的 registerBeanDefinitions()方法,首先先获取到注解所标识的类,然后将这个类所在的包以及子包的名称放入到一个String数组当中,再将该String数组中的包的所有组件导入到容器当中。
2.在@EnableAutoConfiguration中,还有@Import({AutoConfigurationImportSelector.class})
重点就是扫描spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面的META-INF/spring.factories,文件里面写死了spring-boot一启动就要给容器中加载的所有配置类xxxAutoConfiguration,总共127个。(个数的话,不同的版本是不同的)。每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值,从xxxxProperties里面拿。xxxProperties通过@ConfigurationProperties注解和配置文件进行了绑定。
虽然这127个场景的所有自动配置在启动的时候默认全部加载。但不会全部生效。最终某一个xxxxAutoConfiguration会按照条件装配规则(@Conditional)进行生效,实现按需开启。
SpringBoot如何向容器注册bean
1.springboot是通过启动类上的@ComponentScan注解所指定的包路径来进行扫描的,如果没有标注这个注解,则默认从启动类所在的包路径开始扫描,会根据类的注解来判断是否需要被容器进行管理
2.将扫描到的类封装成BeanDefinition并注册到BeanFactory容器
3.实例化所有的BeanDefinition,其中包括解决循环依赖、延迟加载问题
1.对于@Component
根据启动类的注解@SpringBootApplication会默认加载和Application类所在同一个目录下的所有类,包括所有子目录下的类。将这些类纳入到spring容器。(当启动类和@Component分开时,如果启动类在某个包下,需要在启动类中增加注解@ComponentScan,配置需要扫描的包名。)
扫描到之后就是Spring加载bean的过程了。
2.对于@Configuration+@Bean 第三方包里面的组件、将其他jar包中的类
3.@Import(要导入到容器中的组件);容器会自动注册这个组件,id默认是全类名。(@Import是Spring的注解。)
ImportSelector:返回需要导入的组件的全类名数组;
ImportBeanDefinitionRegistrar:手动注册bean到容器中
如何解决Maven依赖冲突
依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成类包版本冲突。
首先查看产生依赖冲突的类jar,其次找出我们不想要的依赖类jar,手工将其排除在外就可以了。
通过dependency:tree是命令来检查版本冲突。idea,可以安装maven helper插件。
项目会引用5.2.7.RELEASE的spring core jar包,而不会引用5.2.0的jar包,如果我们想用5.2.0版本的spring core包,我们该如何做?
a、使用第一声明者优先原则
谁先定义的就用谁的传递依赖,即在pom.xml文件自上而下,先声明的jar坐标,就先引用该jar的传递依赖。pom文件中,5.2.0的写在5.2.7的上面
b、使用路径近者优先原则
即直接依赖级别高于传递依赖。
c、排除依赖
排除依赖如果是idea,可以使用maven helper插件进行排除。点开pom.xml,切换到Dependency Analyzer视图,选择All Dependencies as Tree,点击要排除的jar,右键会出现Execlude选项
4、版本锁定
使用dependencyManagement 进行版本锁定,dependencyManagement可以统一管理项目的版本号,确保应用的各个项目的依赖和版本一致。
为什么SpringBoot可以直接运行 jar 包
JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用来指示工具如何处理特定的 JAR。
使用SpringBoot打过jar包,目标文件会生成两个文件,一个是以.jar的包,一个是.jar.original文件。
.jar.original是maven在SpringBoot重新打包之前的原始包,内部只包含了项目的用户类,不包含其他的依赖jar包。SpringBoot重新打包之后,最后生成.jar包,内部包含了原始jar包以及其他的引用依赖。
一、jar包的内部结构
SpringBoot打出的jar包,可以直接通过解压的方式查看内部的构造。一般情况下有三个目录。
BooT-INF:这个文件夹下有两个文件夹classes用来存放用户类,也就是原始jar.original里的类;还有一个是lib,就是这个原始jar.original引用的依赖。
META-INF:这里是通过java-jar启动的入口信息,记录了入口类的位置等信息。
org: Springboot loader的代码,通过它来启动。
二、加载过程
从/BOOT-INF/MANIFEST.MF
文件中的Main-class
指向入口开始。创建JarLauncher
并且通过它的launch()
方法开始加载jar
包内部信息。
Main-Class:记录了java -jar的启动入口,当使用该命令启动时就会调用这个入口类的main方法。
JarLauncher:Launcher是启动程序的基类,JarLauncher的空构造方法,在创建时由父类ExecutableArchiveLauncher的构造方法去加载文件。
launch()方法:
1.有注册URL协议的处理器,没有指定时,默认指向org.springframework.boot.loader包路径,注册协议 2.获取类路径下的归档文件Archive并通过这些归档文件的URL,创建线程上下文类加载器LaunchedURLClassLoader 3.使用类加载器和用户编写的启动入口类,通过反射调用它的main方法。
总结:
执行 java -jar xx.jar 命令时,会去 解析 MATE-INF 文件夹中的 MANIFEST.MF 清单文件,然后找到 Main-class ,反射运行其中的 main 方法。这个是最根本的原因。
而 Springboot maven 插件打包后的 jar 包结构有所变动,新增 org loader 代码目录和 BOOT-INF 目录,META-INF 目录不变,但是其中的 MANIFEST.MF 发生改变,其中新增 Start-Class 表示真正的启动类,而原本的 Main-Class 则指向 JarLauncher , JarLauncher 启动时会去 注册协议,创建 ClassLoader,加载并反射运行 Start-Class 中的 main 方法,来启动程序。 重写 Jar 协议是在 Spring boot loader源码中的 JarFile 中进行的,同时重新实现 URLStreamHandler 来解决 嵌套Jar 的问题。
springboot调用第三方接口
1.使用spring3.0提供的HTTP 请求工具RestTemplate进行接口的调用
2.使用java.net自带的HttpURLConnection进行第三方接口的调用
3.java11使用了HttpClient调用第三方接口:相比HttpURLConnection,其方法更简便,并且支持发送异步请求
Spring Boot读取配置文件中数据
1,使用@Value获取配置文件内容
2,使用@ConfigurationProperties获取配置文件内容
3,使用Environment对象
springboot读取配置文件的三种方式_springboot如何读取配置文件_cgv3的博客-CSDN博客