Spring
一句话概括:Spring 是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架。
Spring有哪些特性呢?
- IOC 和 DI 的支持
Spring 的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring 工厂用于生成Bean,并且管理 Bean 的生命周期,实现高内聚低耦合的设计理念。
- AOP 编程的支持
Spring 提供了面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等切面功能。
- 声明式事务的支持
支持通过配置就来完成对事务的管理,而不需要通过硬编码的方式,以前重复的一些事务提交、回滚的JDBC代码,都可以不用自己写了。
- 快捷测试的支持
Spring 对 Junit 提供支持,可以通过注解快捷地测试 Spring 程序。
- 快速集成功能
方便集成各种优秀框架,Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持。
- 复杂API模板封装
Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了模板化的封装,这些封装 API 的提供使得应用难度大大降低。
Spring有哪些常用注解呢?
Web:
@Controller:组合注解(组合了@Component注解),应用在MVC层(控制层)。
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
@RequestMapping:用于映射Web请求,包括访问路径和参数。如果是Restful风格接口,还可以根据请求类型使用不同的注解:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@ResponseBody:支持将返回值放在response内,而不是一个页面,通常用户返回json数据。
@RequestBody:允许request的参数在request体中,而不是在直接连接在地址后面。
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
容器:
@Component:表示一个带注释的类是一个“组件”,成为Spring管理的Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解。
@Service:组合注解(组合了@Component注解),应用在service层(业务逻辑层)。
@Repository:组合注解(组合了@Component注解),应用在dao层(数据访问层)。
@Autowired:Spring提供的工具(由Spring的依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入)。
@Qualifier:该注解通常跟 @Autowired 一起使用,当想对注入的过程做更多的控制,@Qualifier 可帮助配置,比如两个以上相同类型的 Bean 时 Spring 无法抉择,用到此注解
@Configuration:声明当前类是一个配置类(相当于一个Spring配置的xml文件)
@Value:可用在字段,构造器参数跟方法参数,指定一个默认值,支持 #{} 跟 ${} 两个方式。一般将 SpringbBoot 中的 application.properties 配置的属性值赋值给变量。
@Bean:注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy。
@Scope:定义我们采用什么模式去创建Bean(方法上,得有@Bean) 其设置类型包括:Singleton 、Prototype、Request 、 Session、GlobalSession。
AOP:
@Aspect:声明一个切面(类上) 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After :在方法执行之后执行(方法上)。
@Before:在方法执行之前执行(方法上)。
@Around:在方法执行之前与之后执行(方法上)。
@PointCut:声明切点 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)。
事务:
@Transactional:在要开启事务的方法上使用@Transactional注解,即可声明式开启事务。
Spring 中应用了哪些设计模式呢?
1 工厂模式 : Spring 容器本质是一个大工厂,使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
2 代理模式 : Spring AOP 功能功能就是通过代理模式来实现的,分为动态代理和静态代理。
3 单例模式 : Spring 中的 Bean 默认都是单例的,这样有利于容器对Bean的管理。
4 模板模式 : Spring 中 JdbcTemplate、RestTemplate 等以 Template结尾的对数据库、网络等等进行操作的模板类,就使用到了模板模式。
5 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
6 适配器模式 :Spring AOP 的增强或通知 (Advice) 使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配 Controller。
7 策略模式:Spring中有一个Resource接口,它的不同实现类,会根据不同的策略去访问资源。
说一说什么是IOC?什么是DI?
Java 是面向对象的编程语言,一个个实例对象相互合作组成了业务逻辑,原来,我们都是在代码里创建对象和对象的依赖。
所谓的IOC(控制反转):就是由容器来负责控制对象的生命周期和对象间的关系。以前是我们想要什么,就自己创建什么,现在是我们需要什么,容器就给我们送来什么。
也就是说,控制对象生命周期的不再是引用它的对象,而是容器。对具体对象,以前是它控制其它对象,现在所有对象都被容器控制,所以这就叫控制反转。
DI(依赖注入):指的是容器在实例化对象的时候把它依赖的类注入给它。有的说法IOC和DI是一回事,有的说法是IOC是思想,DI是IOC的实现。
为什么要使用IOC呢?
最主要的是两个字解耦,硬编码会造成对象间的过度耦合,使用IOC之后,我们可以不用关心对象间的依赖,专心开发应用就行。
能简单说一下Spring IOC的实现机制吗?
ioc的实现机制:简单工厂+反射
什么是工厂模式:很简单,就是调用一个方法(工厂方法)根据传入的参数返回一个对象。
IOC简单工厂+反射:
简单工厂就是调用这个方法 BeanFactory.getBean(name);
反射就是在工厂模式getBean()方法中通过反射的方式来创建Bean。
反射会根据getBean(name),传入的name来通过以下两种配置方式找到类全路径然后进行创建Bean对象。
常用有两种配置Bean的方式:
1、 通过spring.xml配置
<bean id="userDao" class="com.test.***.userDao"/>
2、通过注解配置
@Component("userDao")、@Service等
说说BeanFactory和ApplicantContext?
可以这么形容,BeanFactory是Spring的“心脏”,ApplicantContext是完整的“身躯”。
BeanFactory和ApplicantContext的比喻
BeanFactory(Bean工厂)是Spring框架的基础设施,面向Spring本身。
ApplicantContext(应用上下文)建立在BeanFactoty基础上,面向使用Spring框架的开发者。
BeanFactory 接口
BeanFactory是类的通用工厂,可以创建并管理各种类的对象。
Spring为BeanFactory提供了很多种实现,最常用的是XmlBeanFactory,但在Spring 3.2中已被废弃,建议使用XmlBeanDefinitionReader、DefaultListableBeanFactory。
Spring5 BeanFactory继承体系
BeanFactory接口位于类结构树的顶端,它最主要的方法就是getBean(String var1),这个方法从容器中返回特定名称的Bean。
BeanFactory的功能通过其它的接口得到了不断的扩展,比如AbstractAutowireCapableBeanFactory定义了将容器中的Bean按照某种规则(比如按名字匹配、按类型匹配等)进行自动装配的方法。
这里看一个 XMLBeanFactory(已过期) 获取bean 的例子:
public class HelloWorldApp{
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory (new ClassPathResource("beans.xml"));
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
obj.getMessage();
}
}
ApplicationContext 接口
ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。可以这么说,使用BeanFactory就是手动档,使用ApplicationContext就是自动档。
Spring5 ApplicationContext部分体系类图
ApplicationContext 继承了HierachicalBeanFactory和ListableBeanFactory接口,在此基础上,还通过其他的接口扩展了BeanFactory的功能,包括:
Bean instantiation/wiring
Bean 的实例化/串联
自动的 BeanPostProcessor 注册
自动的 BeanFactoryPostProcessor 注册
方便的 MessageSource 访问(i18n)
ApplicationEvent 的发布与 BeanFactory 懒加载的方式不同,它是预加载,所以,每一个 bean 都在 ApplicationContext 启动之后实例化
这是 ApplicationContext 的使用例子:
public class HelloWorldApp{
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
ApplicationContext 包含 BeanFactory 的所有特性,通常推荐使用前者。
你知道Spring容器启动阶段会干什么吗?
Spring的IOC容器工作的过程,其实可以划分为两个阶段:容器启动阶段和Bean实例化阶段。
其中容器启动阶段主要做的工作是加载和解析配置文件,保存到对应的Bean定义中。
容器启动开始,首先会通过某种途径加载Congiguration MetaData,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Congiguration MetaData进行解析和分析,并将分析后的信息组为相应的BeanDefinition。
最后把这些保存了Bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动就完成了。
能说一下Spring Bean生命周期吗?
在Spring中,基本容器BeanFactory和扩展容器ApplicationContext的实例化时机不太一样,BeanFactory采用的是延迟初始化的方式,也就是只有在第一次getBean()的时候,才会实例化Bean;ApplicationContext启动之后会实例化所有的Bean定义。
Spring IOC 中Bean的生命周期大致分为四个阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialization)、销毁(Destruction)。
再来看一个稍微详细一些的过程:
实例化:第 1 步,实例化一个 Bean 对象
属性赋值:第 2 步,为 Bean 设置相关属性和依赖
初始化:初始化的阶段的步骤比较多,5、6步是真正的初始化,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,初始化完成之后,Bean就可以被使用了
销毁:第 8~10步,第8步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第9、10步真正销毁 Bean 时再执行相应的方法
简单总结一下,Bean生命周期里初始化的过程相对步骤会多一些,比如前置、后置的处理。
最后通过一个实例来看一下具体的细节:
Bean定义和依赖定义有哪些方式?
直接编码方式:我们一般接触不到直接编码的方式,但其实其它的方式最终都要通过直接编码来实现。
配置文件方式:通过xml、propreties类型的配置文件,配置相应的依赖关系,Spring读取配置文件,完成依赖关系的注入。
注解方式:注解方式应该是我们用的最多的一种方式了,在相应的地方使用注解修饰,Spring会扫描注解,完成依赖关系的注入。
有哪些依赖注入的方法?
Spring支持构造方法注入、属性注入、工厂方法注入,其中工厂方法注入,又可以分为静态工厂方法注入和非静态工厂方法注入。
构造方法注入
通过调用类的构造方法,将接口实现类通过构造方法变量传入
属性注入:通过Setter方法完成调用类所需依赖的注入
工厂方法注入
静态工厂注入
静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让 Spring 管理所有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过 Spring 注入的形式获取:
非静态工厂注入
非静态工厂,也叫实例工厂,意思是工厂方法不是静态的,所以我们需要首先 new 一个工厂实例,再调用普通的实例方法。
Spring有哪些自动装配的方式?
什么是自动装配?
Spring IOC容器知道所有Bean的配置信息,此外,通过Java反射机制还可以获知实现类的结构信息,如构造方法的结构、属性等信息。掌握所有Bean的这些信息后,Spring IOC容器就可以按照某种规则对容器中的Bean进行自动装配,而无须通过显式的方式进行依赖配置。
Spring提供的这种方式,可以按照某些规则进行Bean的自动装配,元素提供了一个指定自动装配类型的属性:autowire="<自动装配类型>"
Spring提供了4种自动装配类型:
byName:根据名称进行自动匹配,假设Boss有一个名为car的属性,如果容器中刚好有一个名为car的bean,Spring就会自动将其装配给Boss的car属性
byType:根据类型进行自动匹配,假设Boss有一个Car类型的属性,如果容器中刚好有一个Car类型的Bean,Spring就会自动将其装配给Boss这个属性
constructor:与 byType类似,只不过它是针对构造函数注入而言的。如果Boss有一个构造函数,构造函数包含一个Car类型的入参,如果容器中有一个Car类型的Bean,则Spring将自动把这个Bean作为Boss构造函数的入参;如果容器中没有找到和构造函数入参匹配类型的Bean,则Spring将抛出异常。
autodetect:根据Bean的自省机制决定采用byType还是constructor进行自动装配,如果Bean提供了默认的构造函数,则采用byType,否则采用constructor。
Spring中的Bean的作用域有哪些?
Spring的Bean主要支持五种作用域:
singleton :在Spring容器仅存在一个Bean实例,Bean以单实例的方式存在,是Bean默认的作用域。
prototype : 每次从容器重调用Bean时,都会返回一个新的实例。
以下三个作用域于只在Web应用中适用:
request : 每一次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP Request内有效。
session : 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。
globalSession:同一个全局Session共享一个Bean,只用于基于Protlet的Web应用,Spring5中已经不存在了。
Spring 中的单例 Bean 会存在线程安全问题吗?
Spring中的单例Bean不是线程安全的。
因为单例Bean,是全局只有一个Bean,所有线程共享。如果说单例Bean,是一个无状态的,也就是线程中的操作不会对Bean中的成员变量执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
假如这个Bean是有状态的,也就是会对Bean中的成员变量进行写操作,那么可能就存在线程安全的问题。
单例Bean线程安全问题怎么解决呢?
将Bean中的成员变量保存在ThreadLocal中⭐
我们知道ThredLoca能保证多线程下变量的隔离,可以在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal里,这是推荐的一种方式。
说说循环依赖?
什么是循环依赖?Spring 循环依赖:简单说就是自己依赖自己,或者和别的Bean相互依赖。
只有单例的Bean才存在循环依赖的情况,原型(Prototype)情况下,Spring会直接抛出异常。原因很简单,AB循环依赖,A实例化的时候,发现依赖B,创建B实例,创建B的时候发现需要A,创建A1实例……无限套娃,直接把系统干垮。
Spring可以解决哪些情况的循环依赖?
Spring不支持基于构造器注入的循环依赖,但是假如AB循环依赖,如果一个是构造器注入,一个是setter注入呢?
第四种可以而第五种不可以的原因是 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。
所以简单总结,当循环依赖的实例都采用setter方法注入的时候,Spring可以支持,都采用构造器注入的时候,不支持,构造器注入和setter注入同时存在的时候,看天。
那Spring怎么解决循环依赖的呢?
(开发人员做好设计,别让Bean循环依赖)把Bean的实例化和Bean中属性的依赖注入分离出来
单例Bean初始化完成,要经历三步:
注入就发生在第二步,属性赋值,结合这个过程,Spring 通过三级缓存解决了循环依赖:
一级缓存 : Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例
二级缓存 : Map<String,Object> earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例
三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。
我们来看一下三级缓存解决循环依赖的过程:
当 A、B 两个类发生循环依赖时:
A实例的初始化过程:
1.创建A实例,实例化的时候把A对象⼯⼚放⼊三级缓存,表示A开始实例化了
- A注⼊属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B
- 同样,B注⼊属性时发现依赖A,它就会从缓存里找A对象。依次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,发现A虽然不太完善,但是存在,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入⼀级缓存。
- 接着A继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存
- 最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象
所以,我们就知道为什么Spring能解决setter注入的循环依赖了,因为实例化和属性赋值是分开的,所以里面有操作的空间。如果都是构造器注入的化,那么都得在实例化这一步完成注入,所以自然是无法支持了。
为什么要三级缓存?⼆级不⾏吗?
在没有 AOP 的情况下,确实可以只使用一级和三级缓存来解决循环依赖问题。但是,当涉及到 AOP 时,二级缓存就显得非常重要了,因为它确保了即使在 Bean 的创建过程中有多次对早期引用的请求,也始终只返回同一个代理对象,从而避免了同一个 Bean 有多个代理对象的问题。
@Autowired的实现原理?
实现@Autowired的关键是:AutowiredAnnotationBeanPostProcessor
在Bean的初始化阶段,会通过Bean后置处理器来进行一些前置和后置的处理。
实现@Autowired的功能,也是通过后置处理器来完成的。这个后置处理器就是AutowiredAnnotationBeanPostProcessor。
Spring在创建bean的过程中,最终会调用到doCreateBean()方法,在doCreateBean()方法中会调用populateBean()方法,来为bean进行属性填充,完成自动装配等工作。
在populateBean()方法中一共调用了两次后置处理器,第一次是为了判断是否需要属性填充,如果不需要进行属性填充,那么就会直接进行return,如果需要进行属性填充,那么方法就会继续向下执行,后面会进行第二次后置处理器的调用,这个时候,就会调用到AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues()方法,在该方法中就会进行@Autowired注解的解析,然后实现自动装配。
说说什么是AOP?
AOP:面向切面编程。简单说,就是把一些业务逻辑中的相同的代码抽取到一个独立的模块中,让业务逻辑更加清爽。
AOP 的核心其实就是动态代理,如果是实现了接口的话就会使用 JDK 动态代理,否则使用 CGLIB 代理,主要应用于处理一些具有横切性质的系统级服务,如日志收集、事务管理、安全检查、缓存、对象池管理等。
Spring事务的传播机制和隔离级别
Spring事务 事务是逻辑处理原⼦性的保证⼿段,通过使⽤事务控制,可以极⼤的避免出现逻辑处理失败导致的脏数据等问题。
事务最重要的两个特性,是事务的传播级别和数据隔离级别。传播级别定义的是事务的控制范围,事务隔离级别定义的是事务在数据库读写⽅⾯的控制范围。
事务传播机制
1)PROPAGATION_REQUIRED,Spring默认的事务传播级别,使⽤该级别的特点是,如果上下⽂中已经存在事务,那么就加⼊到事务中执⾏,如果当前上下⽂中不存在事务,则新建事务执⾏。所以这个级别通常能满⾜处理⼤多数的业务场景。
2)PROPAGATION_SUPPORTS ,从字⾯意思就知道,supports,⽀持,该传播级别的特点是,如果上下⽂存在事务,则⽀持事务加⼊事务,如果没有事务,则使⽤⾮事务的⽅式执⾏。所以说,并⾮所有的包在transactionTemplate.execute中的代码都会有事务⽀持。这个通常是⽤来处理那些并⾮原⼦性的⾮核⼼业务逻辑操作。应⽤场景较少。
3)PROPAGATION_MANDATORY ,该级别的事务要求上下⽂中必须要存在事务,否则就会抛出异常!配置该⽅式的传播级别是有效的控制上下⽂调⽤代码遗漏添加事务控制的保证⼿段。⽐如⼀段代码不能单独被调⽤执⾏,但是⼀旦被调⽤,就必须有事务包含的情况,就可以使⽤这个传播级别。
4)PROPAGATION_REQUIRES_NEW ,从字⾯即可知道,new,每次都要⼀个新事务,该传播级别的特点是,每次都会新建⼀个事务,并且同时将上下⽂中的事务挂起,执⾏当前新建事务完成以后,上下⽂事务恢复再执⾏。
这是⼀个很有⽤的传播级别,举⼀个应⽤场景:现在有⼀个发送100个红包的操作,在发送之前,要做 ⼀些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送⽇志,发送⽇志要求 100%的准确,如果⽇志不准确,那么整个⽗事务逻辑需要回滚。 怎么处理整个业务需求呢?就是通过这个
PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以 完成。发送红包的⼦事务不会直接影响到⽗事务的提交和回滚。
5)PROPAGATION_NOT_SUPPORTED ,这个也可以从字⾯得知,not supported ,不⽀持,当前级 别的特点就是上下⽂中存在事务,则挂起事务,执⾏当前逻辑,结束后恢复上下⽂的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩⼩。我们知道⼀个事务越⼤,它存在的⻛险也就越 多。所以在处理事务的过程中,要保证尽可能的缩⼩范围。⽐如⼀段代码,是每次逻辑操作都必须调⽤ 的,⽐如循环1000次的某个⾮核⼼业务逻辑操作。这样的代码如果包在事务中,势必造成事务太⼤,导 致出现⼀些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上⽤场了。⽤当前级别的 事务模板抱起来就可以了。
6)PROPAGATION_NEVER ,该事务更严格,上⾯⼀个事务传播级别只是不⽀持⽽已,有事务就挂起,⽽PROPAGATION_NEVER传播级别要求上下⽂中不能存在事务,⼀旦有事务,就抛出runtime异 常,强制停⽌执⾏!这个级别上辈⼦跟事务有仇。
7)PROPAGATION_NESTED ,字⾯也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下⽂中存在事务,则嵌套事务执⾏,如果不存在事务,则新建事务
事务隔离级别
1、Serializable :最严格的级别,事务串⾏执⾏,资源消耗最⼤;
2、REPEATABLE READ :保证了⼀个事务不会修改已经由另⼀个事务读取但未提交(回滚)的数据。 避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
3、READ COMMITTED :⼤多数主流数据库的默认事务等级,保证了⼀个事务不会读到另⼀个并⾏事务 已修改但未提交的数据,避免了“脏读取”。该级别适⽤于⼤多数系统。
4、Read Uncommitted :保证了读取过程中不会读取到⾮法数据。 上⾯的解释其实每个定义都有⼀些拗⼝,其中涉及到⼏个术语:脏读、不可重复读、幻读。
脏读 :所谓的脏读,其实就是读到了别的事务回滚前的脏数据。⽐如事务B执⾏过程中修改了数据X,在未提交前,事务A读取了X,⽽事务B却回滚了,这样事务A就形成了脏读。
不可重复读 :不可重复读字⾯含义已经很明了了,⽐如事务A⾸先读取了⼀条数据,然后执⾏逻辑的候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。
幻读 :⼩的时候数⼿指,第⼀次数⼗10个,第⼆次数是11个,怎么回事?产⽣幻觉了? 幻读也是这样⼦,事务A⾸先根据条件索引得到10条数据,然后事务B改变了数据库⼀条数据,导致也 符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产⽣了幻读。
Spring MVC 的核心组件?
DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥。
Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。
HandlerMapping:DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求映射到不同的 Handler。
HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerApater 来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
ViewResolver:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端。
描述⼀下SpringMVC的执⾏流程
1.客户端向服务端发送一次请求,这个请求会先到前端控制器DispatcherServlet(也叫中央控制器)。
2.DispatcherServlet接收到请求后会调用HandlerMapping处理器映射器。由此得知,该请求该由哪个Controller来处理(并未调用Controller,只是得知)
3.DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该要去执行哪个Controller
4.HandlerAdapter处理器适配器去执行Controller并得到ModelAndView(数据和视图),并层层返回给DispatcherServlet
5.DispatcherServlet将ModelAndView交给ViewReslover视图解析器解析,然后返回真正的视图。
6.DispatcherServlet将模型数据填充到视图中
7.DispatcherServlet将结果响应给客户端
SpringMVC中常⽤的注解有哪些,作⽤是什么
1. @Controller
@Controller注解在类上,表明这个类是Spring MVC⾥的Controller,将其声明为Spring的⼀个 Bean,DispatchServlet会⾃动扫描注解了此注解的类,并将Web请求映射到注解了 @RequestMapping的⽅法上,需要注意的是,在Spring MVC声明控制器Bean的时候,只能使⽤ @Controller。
2. @RequestMapping
@RequestMapping注解是⽤来映射Web请求(访问路径和参数)、处理类和⽅法的。它可以注解 在类和⽅法上。注解在⽅法上的@RequestMapping路径会继承注解在类上的路径, @RequestMapping⽀持Servlet的request和response作为参数,也⽀持对它们的媒体类型进⾏配 置。
3. @ResponseBody
@ResponseBody⽀持将返回值放在response体内,⽽不是返回⼀个⻚⾯。Ajax的异步访问程序,可以以此注解返回的是数据⽽不是返回⻚⾯;此注解可以放在返回值或者⽅法上。
4. @RequestBody
@RequestBody允许request的参数在request体中,⽽不是在直接链接在地址后⾯。此注解放在参数前。@RequestBody的作⽤其实是将json格式的数据转为java对象
5. @PathVariable
@PathVariable ⽤来接收路径参数,如/news/001,可接收001作为参数,此注解放置在参数前。
6. @RestController
@RestController是⼀个组合注解,组合了@Controller和@ResponseBody,意味着当只开发⼀个 和⻚⾯交互数据的控制的时候,需要使⽤此注解。若没有此注解,要想实现上述功能,则需⾃⼰ 在代码中加@Controller和@ResponseBody两个注解。
什么是SpringMVC 拦截器
拦截器会对处理器进⾏拦截,这样通过拦截器就可以增强处理器的功能。Spring MVC中,所有的拦截器都需要实现HandlerInterceptor接⼝,该接⼝包含如下三个⽅法:preHandle()、postHandle()、afterCompletion()。通过上图可以看出,Spring MVC拦截器的执⾏流程如下:
执⾏preHandle⽅法,它会返回⼀个布尔值。如果为false,则结束所有流程,如果为true,则执⾏下⼀步。
执⾏处理器逻辑,它包含控制器的功能。
执⾏postHandle⽅法。
执⾏视图解析和视图渲染。
执⾏afterCompletion⽅法。
Spring MVC拦截器的开发步骤如下:
1. 开发拦截器:
实现handlerInterceptor接⼝,从三个⽅法中选择合适的⽅法,实现拦截时要执⾏的具体业务逻辑。
2. 注册拦截器:
定义配置类,并让它实现WebMvcConfigurer接⼝,在接⼝的addInterceptors⽅法中,注册拦截器,并定义该拦截器匹配哪些请求路径。
MyBatis
JDBC编程有哪些不⾜之处,MyBatis是如何解决这些问题的?
1. 数据库连接创建、释放频繁造成系统资源浪费从⽽影响系统性能,如果使⽤数据库连接池可解决此 问题。解决:在 mybatis-config.xml 中配置数据连接池,使⽤连接池管理数据库连接。
2. Sql语句写在代码中造成代码不易维护,实际应⽤sql变化的可能较⼤,sql变动需要改变java代码。解决:将Sql语句配置在 XXXXmapper.xml ⽂件中与java代码分离。
3. 向sql语句传参数麻烦,因为sql语句的where条件不⼀定,可能多也可能少,占位符需要和参数⼀⼀对应。解决:Mybatis⾃动将java对象映射⾄sql语句。
4. 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析⽐较⽅便。解决:Mybatis⾃动将sql执⾏结果映射⾄java对象。
#{} 和 ${} 的区别是什么?
${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于原样文本替换,可以替换任意内容,比如${driver}会被原样替换为com.mysql.jdbc. Driver。
#{}是 sql 的参数占位符,MyBatis 会将 sql 中的#{}替换为? 号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的? 号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()。
xml 映射文件中,除了常见的 select、insert、update、delete 标签之外,还有哪些标签?
还有很多其他的标签, <resultMap>、 <parameterMap>、 <sql>、 <include>、 <selectKey> ,加上动态 sql 的 9 个标签, trim|where|set|foreach|if|choose|when|otherwise|bind 等,其中 <sql> 为 sql 片段标签,通过 <include> 标签引入 sql 片段, <selectKey> 为不支持自增的主键生成策略标签。
Mybatis中Dao接⼝和XML⽂件的SQL如何建⽴关联
1. Mybatis会把每个SQL标签封装成SqlSource对象,XML⽂件中的每⼀个SQL标签就对应⼀个MappedStatement对象,这⾥⾯有两个属性很重要。
id:全限定类名+⽅法名组成的ID。
sqlSource:当前SQL标签对应的SqlSource对象。
2. Dao接⼝的⼯作原理是JDK动态代理,Mybatis运⾏时会使⽤JDK动态代理为Dao接⼝⽣成代理proxy对象,代理对象proxy会拦截接⼝⽅法,转⽽执⾏ MappedStatement 所代表的sql,然后将 sql执⾏结果返回。 Mapper接⼝是没有实现类的,当调⽤接⼝⽅法时,接⼝全限名+⽅法名拼接字符串作为key 值,可唯⼀定位⼀个 MappedStatement
举例:com.mybatis3.mappers.StudentDao.findStudentById,
找到 namespace为 com.mybatis3.mappers.StudentDao下⾯的 id = findStudentById的
MappedStatement。
Mybatis动态sql是做什么的?都有哪些动态sql?
MyBatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。MyBatis 提供了 9 种动态 sql 标签:<if></if><where></where>(trim,set)<choose></choose>(when, otherwise)<foreach></foreach><bind/>
MyBatis和Hibernate有哪些不同
Mybatis和hibernate不同,它不完全是⼀个ORM框架,因为MyBatis需要程序员⾃⼰编写Sql语 句。
Mybatis直接编写原⽣态sql,可以严格控制sql执⾏性能,灵活度⾼,⾮常适合对关系数据模型要 求不⾼的软件开发,因为这类软件需求变化频繁,⼀但需求变化要求迅速输出成果。但是灵活的前 提是mybatis⽆法做到数据库⽆关性,如果需要实现⽀持多种数据库的软件,则需要⾃定义多套 sql映射⽂件,⼯作量⼤。
Hibernate对象/关系映射能⼒强,数据库⽆关性好,对于关系模型要求⾼的软件,如果⽤ hibernate开发可以节省很多代码,提⾼效率。
MyBatis中接⼝绑定有⼏种实现⽅式,是怎么实现的?
通过注解绑定,在接⼝的⽅法上⾯加上 @Select@Update等注解⾥⾯包含Sql语句来绑定(Sql语句⽐较简单的时候,推荐注解绑定)
通过xml⾥⾯写SQL来绑定, 指定xml映射⽂件⾥⾯的namespace必须为接⼝的全路径名(SQL语句⽐较复杂的时候,推荐xml绑定)
SpringBoot
Spring Boot Starter有什么⽤?
Spring Boot Starter的作⽤是简化和加速项⽬的配置和依赖管理。
Spring Boot Starter可以理解为⼀种预配置的模块,它封装了特定功能的依赖项和配置, ,开发者只需引⼊相关的Starter依赖,⽆需⼿动配置⼤量的参数和依赖项。
Starter还管理了相关功能的依赖项,包括其他Starter和第三⽅库,确保它们能够良好地协同⼯作,避免版本冲突和依赖问题。
Spring Boot Starter的设计使得应⽤可以通过引⼊不同的Starter来实现模块化的开发。每个Starter都关注⼀个特定的功能领域,如数据库访问、消息队列、Web开发等。
开发者可以创建⾃定义的Starter,以便在项⽬中共享和重⽤特定功能的配置和依赖项。
SpringBoot的常⽤注解
肝了一周总结的SpringBoot常用注解大全,一目了然! - 掘金 (juejin.cn)
SpringBoot的启动流程
Spring Boot 应用通常有一个带有 main 方法的主类,这个类上标注了 @SpringBootApplication 注解,它是整个应用启动的入口。这个注解组合了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan,这些注解共同支持配置和类路径扫描。
当执行 main 方法时,首先创建一个 SpringApplication 的实例。这个实例负责管理 Spring 应用的启动和初始化。
SpringApplication.run() 方法负责准备和启动 Spring 应用上下文(ApplicationContext)环境,包括:
扫描配置文件,添加依赖项
初始化和加载 Bean 定义
启动内嵌的 Web 服务器
了解@SpringBootApplication 注解吗?
@SpringBootApplication是 Spring Boot 的核心注解,经常用于主类上,作为项目启动入口的标识。它是一个组合注解:
@SpringBootConfiguration:继承自 @Configuration,标注该类是一个配置类,相当于一个 Spring 配置文件。
@EnableAutoConfiguration:告诉 Spring Boot 根据 pom.xml 中添加的依赖自动配置项目。例如,如果 spring-boot-starter-web 依赖被添加到项目中,Spring Boot 会自动配置 Tomcat 和 Spring MVC。
@ComponentScan:扫描当前包及其子包下被@Component、@Service、@Controller、@Repository 注解标记的类,并注册为 Spring Bean。
SpringBoot 自动配置原理了解吗?
在 Spring 中,自动装配是指容器利用反射技术,根据 Bean 的类型、名称等自动注入所需的依赖。在 Spring Boot 中,开启自动装配的注解是@EnableAutoConfiguration。
Spring Boot 为了进一步简化,直接通过 @SpringBootApplication 注解一步搞定,这个注解包含了 @EnableAutoConfiguration 注解。
- @EnableAutoConfiguration 只是一个简单的注解,但是它的背后却是一个非常复杂的自动装配机制,核心是AutoConfigurationImportSelector 类。
- AutoConfigurationImportSelector实现了ImportSelector接口,这个接口的作用就是收集需要导入的配置类,配合@Import()就将相应的类导入到 Spring 容器中。
- 获取注入类的方法是 selectImports(),找到所有JavaConfig自动配置类的全限定名对应的class,它实际调用的是getAutoConfigurationEntry(),这个方法是获取自动装配类的关键。
Spring Boot 的自动装配原理依赖于 Spring 框架的依赖注入和条件注册,通过这种方式,Spring Boot 能够智能地配置 bean,并且只有当这些 bean 实际需要时才会被创建和配置。