Spring
Spring、Mybatis
谈谈对AOP的理解
- 系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身校心功能之外,这些组件还经常承担者额外的指责。例如日志,事务管理和安全这样的核心服务经第融入到自身具有校心业务理相的组件中去。这些系统服务经第被称为横切关注点,因为它们会路越系統的多个组件。
- 当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力,也就是说,OOP允许你定义从上到下的关系,但井不适合定义从左到右的关系,例如日志功能。
- 日志代码往往水平地散布在所有层次中,而与它所散布到的对象的校心功能无关系。
- 在OOP设计中,已导致了大量代码的重复,而不利于各个模块的重用。
- AOP:将程序中的交叉业务设得(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻得)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情
AOP有哪些实现方式
AOP是通过动态代理实现的,代理模式是一种设计模式,它提供了对目标对象额外的访问方式,即通过代理对象来访问目标对象,这样可以在不修改原目标对象的情况下提供额外的功能。
静态代理与动态代理区别:
静态代理在编译时就实现了,编译完后是一个实际的.class文件。
动态代理是运行时生成的,即编译完以后没有实际的.class文件,而是生成类字节码,并加载到jvm中。
静态代理
指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;
动态代理
在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
- JDK 动态代理:通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是 InvocationHandler 接口和 Proxy 类。
- CGLIB 动态代理:如果月标类没有实现接口,那么spring AoP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意, CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用CGLTB 做动态代理的。
谈谈对IOC的理解
容器概念、控制反转、依赖注入
IOC容器:
实际上就是个map (key, value),里面存的是各种对象(在xml里配置的bean节点、@repository、@service.@controller. @component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入 (autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref厲性根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。
控制反转:
没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上
引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是控制反转这个名称的由来。
全部对象的控制权全部上缴给”第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似”粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个”粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成”粘合剂”的由来。
依赖注入:
获得依赖对象的过程被反转了。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由容器在运行期间,动态地将某种依赖注入到对象之中。
Spring加载Bean的过程
bean的定义信息:xml 注解
BeanFactory FactoryBean区别
BeanFactory:必须遵循完整的Bean生命周期去创建对象,流水线式创建。
FactoryBean:创建对象但是没有标准的流程,类似私人定制。
- isSingleton 判断是否单例
- getObjectType 返回对象的类型
- getObject 返回对象
Spring Bean的生命周期
创建前准备、创建实例、依赖注入、容器缓存、销毁实例
- Spring 容器 从 XML 文件中读取 bean 的定义BeanDefinition,并实例化 bean。
- Spring 根据 bean 的定义填充所有的属性(对对象中加入Autowried注解的属性进行自定义属性填充)。
- 调用Aware方法,如果 bean 实现了 BeanNameAware 接口,Spring 传递 bean 的 ID 到 setBeanName 方法;如果 Bean 实现了 BeanFactoryAware 接口, Spring 传递 beanfactory 给 setBeanFactory 方法。(设置容器属性)
- 如果有任何与 bean 相关联的 BeanPostProcessors,Spring 会在 postProcesserBeforeInitialization()方法内调用它们。(初始化前的方法)
- 如果 bean 实现 IntializingBean 了,调用它的 afterPropertiesSet 方法, 如果 bean 声明了初始化方法,调用此初始化方法。 (初始化方法)
- 如果有 BeanPostProcessors 和 bean 关联,这些 bean 的 postProcessAfterInitialization() 方法将被调用。 (初始化后方法,这里会进行AOP)
- 如果当前创建的bean是单例的,把bean放入单例池
- 使用bean
- 如果 bean 实现了 DisposableBean,它将调用 destroy()方法。
什么是Bean的自动装配,有哪些方式
Spring 容器能够自动装配相互合作的 bean,这意味着容器不需要和配置,能通 过 Bean 工厂自动处理 bean 之间的协作。
1 | <bean id = "book" class = "com.xxx.xxx.Book" autowrire = ""> |
- no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。
- byName:通过参数名 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。
- byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误,使用@Qualifire注解指定 一个去注入。
- constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
- autodetect:首先尝试使用 constructor 来自动装配,如果无法工作, 则使用 byType 方式。
Spring中的Bean是线程安全的吗
Spring本身并没有针对Bean做线程安全的处理,所以
- 如果Bean是无状态的,则Bean是线程安全的
- 有状态,则不安全
另外,Bean是不是线程安全跟Bean作用域没关系,Bean作用域只是表示Bean生命周期的范围
Spring支持的几种bean的作用域
- singleton : bean在每个Spring ioc 容器中只有一个实例。单例模式由BeanFactory自身来维护。该对象的生命周期和IOC一致。(在第一次被注入时才会被创建)
- prototype:为每一个bean请求提供一个实例。在每次注入时都会创建一个新的对象。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。session过期以后bean会随之失效
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
Spring如何解决循环依赖
关键词:三级缓存、提前暴露对象、AOP
- 总:什么是循环依赖?
- A有b属性,B有a属性
- bean的创建过程是先实例化–>初始化
- A在实例化后初始化时b属性为空,去容器中找B对象
- 有B,不存在循环依赖
- 无B,创建B,填充a属性 —>容器中去找A 找不到
- 仔细思考发现A对象是存在的,不过不是一个完整状态,只完成了实例化,没有完成初始化。如果调用了某个对象的引用,后期可以先把非完整状态赋值,等后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用。所以解决问题的核心在于实例化和初始化分开操作
- 当所有对象都完成操作实例化之后,还要把对象放入容器中,此时容器中的对象有两个状态
- 实例化完成但未初始化完成
- 实例化初始化都完成
- 这两种对象都在容器中,所以要用不同的map结构来进行存储,此时就有一级缓存和二级缓存
- 一级缓存放完整的对象
- 二级缓存放非完整对象
- 三级缓存中的value类型是
ObjectFactory
函数式接口,存在的意义是保证在容器中同名的bean对象只有一个,一个对象如果要被代理,或者说要生成代理对象,那么先需要一个普通对象。普通对象和代理对象不能同时出现在容器中,因此一个对象需要被代理时就需要使用代理对象去覆盖之前的普通对象,在实际调用中是没有办法确定什么时候对象被调用,所以就需要当某个对象被调用时优先判断此对象是否需要被代理,类似一种回调机制的实现,因此传入lambda表达式时可以通过lambda表达式来执行对象覆盖过程 - 因此所有bean对象在创建时都放在三级缓存中,后续使用中需要被代理则返回代理对象,不需要则返回普通对象
Spring事务的实现及隔离级别
有两种使用事务的方式:编程式和申明式
- 编程式就是调用一些
API
- 申明式例如
@Transaction(rollback = “”)
Spring事务隔离级别就是数据库的隔离级别。如果数据库配置RC,Spirng配置RR,则以Spring配置为准,如果Spring设置的隔离级别数据库不支持,那么以数据库为准。
Spring Boot、MVC、Spring区别
- spring是一个lOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等
- springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端
- springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制) 、 redis、mongodb、es,可以开箱即用
Spring MVC工作流程
流程说明(重要):
- 客户端(浏览器)发送请求,直接请求到Dispatcherservlet。
- Dispatcherservlet根据请求信息调用HandlerMapping,拿到控制链。<url,handler>的一个Map
- Dispatcherservlet调用HandlerAdapter适配器处理
- 解析到对应的Handler(也就是我们平常说的Controller控制器)
- Controller执行完成返回ModelAndView
- HandlerAdapter会根据把ModelAndView返回给Dispatcherservlet
- ViewResolver会根据逻辑view查找实际的view。
- Dispaterservlet把返回的Model传给view(视图渲染)
- 把view返回给请求者(浏览器)
Spring MVC主要组件
SpringBoot自动配置原理
@SpringBootApplication可以看作是 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:
- @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
- @SpringBootConfiguration:允许在上下文中注册额外的 bean 或导入其他配置类
- @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilter和AutoConfigurationExcludeFilter。
@EnableAutoConfiguration
Spring中有很多Enable开头的注解其作用就是借助@Import来收集并注册特定场景相关的Bean,并加载到IOC容器。
@EnableAutoConfiguration就是借助@lmport来收集所有符合自动配置条件的bean定义,并加载到IOC容器。
- @Import(AutoConfigurationImportSelector.class)
- 帮助SpringBoot将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IOC容器中。
- AutoConfigurationImportSelector类实现了Aware相关接口
- 其中getImports()中调用selectImports()
- @AutoConfigurationPackage
- @Import:导入Registar组件 就干一件事:拿到启动类所在的包名。
SpringBoot常用注解及底层实现原理
1.@SpringBootApplication注解:这个注解标识了一个SpringBoot工程,它实际上是另外三个注解的组合,这三个注解是:
- @SpringBootConfiguration:这个注解实际就是一个@Configuration,表示启动类也是一个配置类
- .@EnableAutoConfiguration:向Spring容器中导入了一个Selector,用来加载Classpath 下SpringFactories中所定义的自动配置类,将这些自动加载为配置Bean
- @ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫描的路径是启动类所在的当前目录
2.@Bean注解:用来定义Bean,类似于XML中的
3.@Controller、@Service、@ResponseBody、@Autowired都可以
如何理解SpringBoot中的starter
- 使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean
- starter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类
- 开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot-starter,spring-boot-starter-redis
Springboot的启动流程细节
- SpringBoot启动的时候,会构造一个SpringApplication的实例,然后调用这个实例的run方法,在run方法调用之前,也就是构造SpringApplication的时候会进行初始化的工作,初始化的时候会做以下几件事:
(1)把参数sources
设置到SpringApplication
属性中,这个sources
可以是任何类型的参数.
(2)判断是否是web程序,并设置到webEnvironment
的boolean
属性中.
(3)创建并初始化ApplicationInitializer
,设置到initializers
属性中 。
(4)创建并初始化ApplicationListene
r,设置到listeners
属性中 。
(5)初始化主类mainApplicatioClass
。 - SpringApplication构造完成之后调用run方法,启动
SpringApplication
,run方法执行的时候会做以下几件事:
(1)构造一个StopWatch
计时器,观察SpringApplication
的执行 。
(2)获取SpringApplicationRunListeners
并封装到SpringApplicationRunListeners
中启动,用于监听run
方法的执行。
(3)创建并初始化ApplicationArguments
,获取run
方法传递的args
参数。
(4)创建并初始化ConfigurableEnvironment
(环境配置)。
(5)打印banner
(只用在Classpath
下添加字符文件图标,就可以在启动时候打印)。
(3)构造Spring容器(ApplicationContext
)上下文。
(4)SpringApplicationRunListeners
发布finish
事件。
(5)StopWatch
计时器停止计时。
MyBatis优缺点
优点:
- 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
- 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
- 很好的与各种数据库兼容(因为MyBatis 使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
- 能够与Spring很好的集成;
- 供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点:
- SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
#{} 和 ${}区别
- #{}是预编译处理是占位符,${}是字符串替换、是拼接符。
- Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement来赋值,会有预编译,#对应的变量自动加上单引号;
- Mybatis在处理${}时,就是把${}替换成变量的值,是动态参数(比如通过传参动态设置表名,动态设置排序字段),调用Statement来赋值,相当于直接拼接,${}对应的变量不会加上单引号;
- 使用#{}可以有效的防止SQL注入,提高系统安全性。
MyBatis二级缓存
Mybatis 中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存 是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以 后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。二级缓存 是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的 sqlsession 是可以共享的