Spring Boot
说说对Spring Boot的理解
从本质上来说,Spring Boot就是Spring,只不过它做了一些配置,简而言之,Spring Boot本身并不提供Spring的核心功能,而是作为Spring的脚手架框架,以达到快速构建项目、预置三方配置、开箱即用的目的。
ApplicationContext的核心功能
核心功能就是获取Bean
ApplicationContext
是继承于BeanFactory
,前者是给开发者用的,后者是给Spring框架用的。
Spring Boot Starter有什么用?
SpringBoot是通过众多的起步依赖(starter)降低项目依赖的复杂度。起步依赖本质上是一个maven项目对象模型,定义了对其他库的传递依赖,这些东西加在一起就支持某项功能。
Spring Boot的启动流程
首先,Springboot项目创建完成会默认生成一个*Application的入口类,我们通过该类的main方法启动SpringBoot项目。
在main方法中,通过SpringApplication的静态方法,即run方法进行SpringApplicaion类的实例化操作,然后再针对实例化对象调用另外一个run方法来完成整个项目的初始化和启动。
SpringApplication调用run方法的大致流程:
其中,SpringApplication在run方法中重点做了以下操作:
- 获取监听器和参数配置
- 打印Banner信息
- 创建并初始化容器
- 监听器发送通知
SpringBoot自动装配过程
整个自动装配过程是:
- SpringBoot通过
@EnableAutoConfiguration
注解开启自动配置。 - 加载Spring.factorier中注册的各种AutoConfiguration类。
- 当某个AutoConfiguration类满足其注解
@Conditional
指定的生效条件(Starter提供的依赖、配置或者Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以依赖框架的自动配置。
说说你对Spring Boot注解的了解
SpringBootApplication注解:
是SpringBoot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableAutoConfiguration
开启了自动配置。
EnableAutoConfiguration注解:主要功能是用来启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的bean,自动配置通常是基于项目classpath中引入的类和已定义的bean来实现。
Conditional注解:这个注解是Spring4.0版本引入的新特性,可以根据是否满足指定的条件来决定是否进行Bean的实例化及装配
、
Spring
Spring的核心是什么
- Spring框架包含众多模块,如Core,Testing,Data Access,Web Servlet等,其中Core是整个Spring框架的核心模块。
- Core模块提供了IOC容器,AOP功能,数据绑定,类型转换等基础功能,而这些功能以及其他模块的功能都是建立在IOC和AOP之上的,所以Spring框架的核心是IOC和AOP
- IOC是控制反转的意思,是一种面向对象的设计思想。IOC可以解决对象之间耦合度过高的问题。
- DI(依赖注入)是IOC的实现方式,也就是说,IOC其实是通过依赖注入实现的。很多时候我们简单的把IOC和DI划等号,这是一种习惯。实现依赖注入的关键是IOC容器,它本质上是一个工厂。
- AOP是面向切面编程,是对面向对象的补充,可以在面向对象的基础上进一步提高编程的效率。简单地说,它可以统一解决一批组件的共性需求(如权限检查,记录日志,事务管理等)。在AOP的思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方,在什么时机调用。当满足调用条件时,AOP会将业务代码织入我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。
说说对Spring容器的了解
Spring主要提供了两种类型的容器,BeanFactory和ApplicationContext。
- BeanFactory:是基础类型的IOC容器,提供完整的IOC服务支持,如果没有特殊指定,默认采用延迟初始化策略。只有当客户端对象需要访问容器中的某个受管对象的时候,才会对该受管对象进行初始化以及依赖注入操作。所以,容器启动初期速度较快,所需要的资源有限。所以对于资源有限,并且功能要求不是很严格的场景,Beanfactory是比较适合的IOC容器。
- ApplicationContext:它是在Beanfactory的基础上构建的,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他的高级特性,比如事件发布,国际化信息支持等。ApplicationContext所管理的对象,在该容器启动以后,默认全部初始化并绑定完成,所以,相对BeanFactory来说,ApplicationContext要求更多的系统资源,同时因为在启动时就要完成所有初始化,容器的启动时间比Beanfactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器比较合适。
说说对BeanFactory的理解
BeanFactory是一个类工厂,与传统类工厂不同的是,BeanFactory是类的通用工厂,它可以创建并管理各种类的对象。这些可被创建和管理的对象本身并没有什么特别之处,仅是一个POJO,Spring称这些被创建和管理的Java对象为Bean。并且,Spring中所说的Bean比JavaBean更为宽泛一些,所有可以被Spring容器实例化并管理的Java类都可称为Bean。
BeanFactory是Spring的顶层接口,最主要的方法是getBean(String beanName)
,该方法从容器中返回特定名称的Bean。
说说对Spring IOC的理解
具体的实现中有三种注入方式,常用的有构造方法注入和setter方法注入,接口注入因为侵入性较强,用的比较少。
Spring是如何管理Bean的
Spring通过IOC容器来管理bean,我们可以通过XML配置或者注解配置,来指导IOC容器对Bean的管理。
管理Bean时常用的注解:
@ComponentScan
用于声明扫描策略,通过它的声明,容器就知道要扫描哪些包下带有声明的类,也可以知道哪些特定的类是被排除在外的,@Component,@Repository,@Service,@Conreoller
用于声明Bean,他们的作用一样,但是语义不同。被这些注解声明的类就可以被容器扫描并创建。@Autowired,@Qualifier
用于注入Bean,即告诉容器应该为当前属性注入哪个Bean。其中,@Autowired
是按照Bean类型进行匹配的,如果这个属性的类型具有多个Bean,就可以通过@Qualifier
指定Bean的名称,以消除歧义。@Scope
用于声明Bean作用域,默认情况下Bean是单例的,即在整个容器中这个类型只有一个实例。可以通过@Scope
注解指定prototype值将其声明为多例的。@PostConstruct,@PreDestory
用于声明Bean的生命周期。其中被@PostConstruct
修饰的方法将在Bean实例化后被调用,@PreDestory
修饰的方法将在容器销毁前被调用。
介绍 Bean 的作用域
默认情况下,Bean在Spring容器中是单例的。我们可以通过@Scope
注解修改Bean的作用域。这个注解有5个取值,分别代表了Bean的5种不同类型的作用域。
- singleton:在Spring容器中仅存在一个实例,即Bean以单例的形式存在。
- prototype:每次调用getBean()时,都会执行new操作,返回一个新的实例。
- request:每次HTTP请求都会创建一个新的Bean
- session:同一个Session共享一个Bean,不同的HTTP Session使用不同的Bean。
- globalSession:同一个全局的Session共享一个Bean。
说一说Bean的生命周期
Spring容器管理Bean,设计对Bean的创建,初始化,调用,销毁等一系列流程,这个流程就是Bean的生命周期。
整个过程是由Spring容器自动管理的,其中有两个环节我们可以进行干预。
- 我们可以自定义初始化方法,并在该方法前增加
@PostConstrct
注解。 - 自定义销毁方法,加上
@PreDestroy
注解,届时Spring容器将在自身销毁前,调用这个方法。
Autowired和Resource注解有什么区别
- Autowired是Spring提供的注解,Resource是JDK提供的注解
- Autowired只能按照类型注入,Resource默认按照名称注入,也支持按照类型注入。
- Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,需要设置它的required属性为false,如果我们想按照名称装配,可以结合
@Qualifer
注解一起使用。Resource中有两个重要的属性,name和type,如果没有指定name属性,默认取属性名作为bean名称寻找依赖对象。需要注意的是,如果Resource没有指定name属性,并且按照默认名称仍然找不到依赖对象时,Resource注解就会回退到按类型装配。但是一旦指定了name属性,就只能按照名称装配了。
Spring是如何解决循环依赖的
首先,需要明确的是,Spring对循环依赖的处理有三种情况:
- 构造器的循环依赖:这种依赖Spring是处理不了的,直接抛出BeanCurrentlyInCreationexception异常。
- 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖
- 非单例循环依赖:无法处理。
所以,我们主要处理的是第二种循环依赖。
Spring 是先将Bean对象实例化(依赖无参构造函数),在设置对象属性的。
Spring中默认提供的单例是线程安全的吗
不是,Spring容器本身并没有提供Bean的线程安全策略。
- 如果单例的Bean是一个无状态的Bean,即线程中的操作不会对Bean的成员执行查询意外的操作,那么这个单例的Bean就是线程安全的。比如Controller,Service,Dao这样的组件,通常都是单例且线程安全的。
- 如果单例Bean是一个有状态的Bean,则可以采用ThreadLocal对状态数据进行线程隔离,来保证线程安全。
有状态的Bean和无状态的Bean
有状态会话bean和无状态会话bean的本质区别是他们的生命期。
有状态会话bean:每个用户都有一个自己特有的一个实例,在用户生存期,bean保持了用户的信息。一旦调用结束或实例结束,bean的生命期也结束。
无状态会话bean:bean一旦初始化就会被加进会话池中,各个用户都可以共用。即便用户已经消亡,Bean的生命期也不一定结束。
public class BeanTest {
public String Hello(){
return "Hello";//我并不知道要给谁说hello
}
public static void main(String[] args) {
BeanTest beanTest = new BeanTest();
System.out.println(beanTest.Hello());
BeanTest2 beanTest2 = new BeanTest2("China");
System.out.println(beanTest2.Hello());
}
}
class BeanTest2{
private String name;
public BeanTest2(String name) {
this.name = name;
}
public String Hello(){
return "Hello "+this.name;//我知道给这个name说hello
}
}
说一说Spring AOP的理解
Spring AOP是面向切面编程,它是一种编程思想,是面向对象编程的一种补充。
面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
所谓切面,就是应用程序之间的横切点,我们可以将其抽象为一个单独的模块。
AOP的术语:
- 连接点(join point):对应的是具体被拦截的对象,因为Spring只支持方法,所以被拦截的对象往往就是指特定的方法,AOP通过动态代理技术把他织入到对应的流程中。
- 切点(point cut):有时候,切面不单单是应用于单个方法,也可能是多个类的不同方法,这时可以通过正则表达式和指示器的规则去定义,从而适配连接点,切点就是这样的一个概念。
- 通知(advice):按照约定流程下的方法,分为前置通知,后置通知,环绕通知,时候返回通知和异常通知,它会根据约定织入流程中。
- 目标对象(target):指被代理对象。
- 引入(introduction):指引入新的类和其方法,增强现有Bean的功能。
- 织入(weaving):通过动态代理技术,为原有的服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按照约定将各类通知织入约定流程的过程。
- 切面(aspect):是一个可以定义切点,各类通知和引入的内容,SpringAOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。
AOP的应用场景
Spring AOP为IOC的使用提供了更多的遍历,一方面,应用可以直接使用AOP的功能,设计应用到横切关注点 ,把跨越应用程序多个模块的功能抽象出来,并通过简单的AOP的使用,灵活的编织到模块中,比如还可以通过AOP实现应用程序的日志功能。另一方面,在Spring内部,一些支持模块也是通过Spring AOP来实现的,比如事务处理。这两个角度就可以看到Spring AOP的核心地位。
既然有没有接口都可以使用CGLib,为什么Spring还要使用JDK动态代理
在性能方面,CGLib创建的代理对象比JDK动态代理创建的代理对象高很多,但是CGLib在创建代理对象时所花费的时间比JDK动态代理多很多。所以,对于单例的对象因为无需频繁创建代理对象,采用CGLib动态代理就比较合适。但是如果是多例的对象,需要频繁的创建代理对象,使用JDK动态代理就比较合适。
Spring如何管理事务
Spring为事务管理提供了一致的编程模板,在高层次上建立了统一的事务抽象。
Spring支持两种事务编程模型:
- 编程式事务
Spring提供了TransactionTemplate模板,利用该模板我们可以通过编程的方式实现事务管理,而无需关注资源获取,复用,释放,事务同步,以及异常处理等操作。相对于声明式事务来说,这种方式相对麻烦一些,但是好在更为灵活,我们可以将事务管理的范围控制的更为精确。
- 声明式事务
它允许我们通过声明的方式,在IOC配置中指定事务的边界和事务属性,Spring会自动在指定的事务边界上应用事务属性,对于编程式事务来说,这种方式十分的方便,只需要在需要做事务的方法上,增加@Transactional
注解,以声明事务特征即可。
Spring的事务传播方式有哪些?
当我们调用一个业务方法时,它的内部可能会调用其他的业务方法,以完成一个完整的业务操作。这种业务操作嵌套调用的时候,如果这两个方法都是要保证事务的,那么就要通过Spring事务传播机制控制当前事务如何传播到被嵌套调用的业务方法中。
事务传播类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,则新建一个事务;如果已存在一个事务,则加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,则以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,则抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行操作,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
基本上根据英文翻译就能知道作用:
Required:必须的。说明必须要有事物,没有就新建事物。
supports:支持。说明仅仅是支持事务,没有事务就非事务方式执行。
mandatory:强制的。说明一定要有事务,没有事务就抛出异常。
required_new:必须新建事物。如果当前存在事物就挂起。
not_supported:不支持事物,如果存在事物就挂起。
never:绝不有事务。如果存在事物就抛出异常
nested: 表示如果当前事务存在,则方法应该运行在一个嵌套事务中。否则,它看起来和 PROPAGATION_REQUIRED 看起来没什么俩样.
SpringMVC
SpringMVC的执行流程
- 整个过程开始于客户端发出的一个HTTP请求,WEB应用服务器接收这个请求,如果匹配DisparcherServlet的请求映射路径,则Web容器将该请求转交给DisparcherServlet处理。
- DIsparcherServlet接收到请求,会根据这个请求的信息(包括URL,HTTP方法,请求报文头,请求参数,Cookie等)以及HandlerMapping的配置找到处理请求的处理器(Handler)。可以将HandlerMapping看做是路由控制器,将Handler看做目标主机。事实上SpringMVC并没有定义一个Handler接口,任意一个Object都可以成为请求处理器。
- 当DisparcherServlet根据HandlerMapping得到当前请求的Handler之后,会通过HandlerAdapter对Handler进行封装,再以统一的接口对各种Handler进行调用。
- 处理完业务逻辑后,将返回一个ModelAndView(含有视图逻辑名和模型数据的信息)给DisparcherServlet。这个逻辑视图名不是真实的视图对象,DIsparcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作。
- 得到真实的视图对象View后,DisparcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染。
- 最后客户端得到的响应可能是HTML或者XML ,JSON等。
SpringMVC的常用注解
@RequestMapping
就是用来处理请求地址映射的,将处理器方法映射到URL路径上
@RequestParam
将请求参数绑定到控制器的方法参数上,是SpringMVC中的接收普通参数的注解。
@RequestBody
如果作用在方法上,就说明返回结果写入HTTP RequeatBody中。
SpringMVC的拦截器
拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。
在SpringMVC中,所有的拦截器都要实现HandlerInterceptor接口。
该接口包含了如下的三个方法,preHandle(), postHandle(), afterCompletion()
所以,如果SpringMVC开发拦截器的话,只要实现handlerInterceptor接口,然后从这三个方法中选择一个合适的方法来实现拦截时要执行的具体业务逻辑。然后定义配置类,并让他实现WebMvcConfigurer接口,在接口的addInterceptor方法中,注册拦截器,并定义该拦截器匹配哪些请求路径。
怎么去做请求拦截
如果是对Controller进行拦截,则可以使用SpringMVC的拦截器
如果对所有的请求进行拦截(比如访问静态资源的请求),可以使用Filter
如果是对除了Controller之外的其他Bean的请求进行拦截,则可以使用Spring AOP