Spring

一.Spring4和Spring5 AOP执行流程差别

advice

1.Spring4

正常执行:@Before======@After======@AfterReturning

异常执行:@Before======@After======@AfterThrowing

2.Spring5

正常执行:@Before(前置通知)=======@AfterReturning(返回后通知)=======@After(后置通知)

异常执行:@Before========@AfterThrowing====@After

二.AOP的底层实现原理

AOP是IOC的扩展功能,先有IOC,AOP是整个流程的一个新增扩展点:BeanPostProcessor(后置处理方法)

1.代理对象的创建过程(advice,切面,切点)

2.通过jdk或cglib的方式来生成代理对象。

面向切面编程,与面向对象编程相辅相成。在OOP中以类为基本单元,而AOP是aspect切面。实现方式有静态代理和动态代理。

(1)静态代理:编译阶段就可生成AOP代理类。

(2)动态代理:运行时生成。分为JDK(利用反射,并且要求被代理的类必须实现一个接口,核心是invocationHandler接口和proxy类)和CGLIB(没有实现接口,就用整个代理目标类,通过继承实现,如果一个类是final修饰无法动态代理)

java动态代理实现与原理详细分析 - Gonjian - 博客园 (cnblogs.com)

三.循环依赖及三级缓存

什么是循环依赖?多个bean之前相互依赖,形成一个闭环。spring容器一般指默认的单例Bean里。

两种注入方式(基于构造方法和单例模式下的setter的依赖注入,非单例无法处理)对循环依赖的影响,避免使用构造方法会抛出异常,spring解决不了,直接抛出异常。我们AB循环依赖问题只要A的注入方式是setter且singleton就不会有循环依赖问题。

spring的单例对象初始化分为:createbeanInstance实例化(调用构造方法);populateBean填充属性(多bean的依赖属性注入);InitializeBean初始化(init方法)。依赖注入就发生在第一二步。

 默认的单例(singleton)的场景是支持循环依赖的,不报错;原型(prototype)的场景是不支持循环依赖的,会报错。

关键在于先把非完整状态的对象优先赋值,即只完成了实例化但是还未初始化。提前暴露不完整对象的引用,先赋值再等后续操作完成赋值。当所有对象完成实例化和初始化操作后,还要把完整对象放到容器里,此时容器里有两个状态,完成实例化还未初始化,完整状态。因为都在容器中,所有要使用不同的map来进行存储,就有了一级缓存和二级缓存。如果一级缓存有了,那么二级缓存中就不会存在同名对象。一级放的是完整对象,二级缓存是非完整对象。

为啥需要三级缓存?三级缓存的value类型是ObjectFactory,是一个函数式接口。存在的意义是保证整个容器的运行工程中同名的bean对象只能有一个。

如果一个对象被代理,或者要生成代理对象,那么要优先生成一个普通对象。两者不能再容器中共存,当一个对象要被代理时,使用代理对象覆盖普通对象。当调用对象时,会回调机制,优先判断对象是否需要被代理。lambda表达式执行对象覆盖。 

  因此,所有bean对象再创建时优先放到三级缓存中,在后续使用中,如果需要被代理则返回代理对象,不然直接返回普通对象。

三级缓存:createBeanInstance之后,addSingletonFactory

二级缓存:第一次从三级缓存确定对象是代理对象还是普通对象,同时删除三级缓存;

一级缓存:生成完整对象后放一级,删除二三级缓存。

spring内部通过三级缓存解决缓存依赖(DefaultSingletonBeanRegistry)

第一级缓存(也叫单例池)singletonObjects:存放已经经历了完成生命周期的bean对象

第二级缓存: earlySingletonObjects,存放早期暴露出来的bean对象,bean的生命周期未结束,也就是已经实例化(申请了内存空间)但未初始化(还没放值)的bean放入该缓存里。

第三级缓存:singletonFactories, 存放可以生成bean的工厂

 refresh()方法就是加载容器初始化的方法。

 四,spring的事务如何回滚

就是怎么实现?(分为编程式事务管理和声明式事务管理)

总:spring的事务是由AOP来实现的,首先要生成具体的代理对象,然后按照AOP的流程执行具体操作。正常要通过通知完成核心功能,但是事务不是,是通过一个TransactionInterceptor(事务拦截器),然后调用invoke(动态代理)实现具体的逻辑。

分:1.准备工作,解析各个方法上事务相关属性,根据属性判断是否开启新事务。

       2.当开启时,获取数据库连接,关闭自动提交,开启事务

      3.执行具体SQL逻辑操作

      4.操作失败通过completeTransactionAfterThrowing,完成事务回滚,具体回滚通过doRollBack方法实现。

     5.没有意外,commiteTransactionAfterThrowing,实现doCommite

五.spring的传播特性

1.使用Spring的好处

(1)轻量,基本版本2M;

(2)控制反转;

(3)AOP;面向切面编程,提取公共业务逻辑,和服务分开;

(4)容器;

(5)MVC框架;

(6)事务管理

(7)异常处理。

2.依赖注入

不必创建对象,但必须描述怎么创建。描述配置文件中哪些组件需要哪些服务,有IOC容器将它们装配在一起。

(1)构造函数注入

(2)setter注入

(3)接口注入

在spring framework中,仅使用构造函数和setter注入。

3.

BeanFactory(主要的有DefaultListableBeanFactory的功能)将利用BeanDefinition来生成bean对象,BeanDefinition相当于BeanFactory的原材料,bean对象就是产品。

4.构造函数和setter注入区别

 5.spring的配置方式

(1)XML配置,以bean标签开头;

(2)注解配置,需要在spring配置文件中启动;

(3)基于Java API配置:通过@Bean和@Configuration

6.spring的bean的作用域

(1)singleton:默认是单例的

(2)prototype:每次请求都会创建一个新的bean实例

(3)request:每次HTTP请求产生一个新的bean,仅在当前HTTP request内有效;

(4)session:在一个session中,一个session对应一个实例。

(5)Global-session

最后三个仅当用户使用applicationcontext可用。

7.如何理解IOC和DI

一个是思想,一个是设计模式。

IOC不用我们自己创建对象,交给bean工厂创建管理,通过面向接口编程的方式来实现对业务组件的动态依赖。IOC是spring争对解决程序耦合存在的。

实际中,spring通过配置文件(如XML或properties)指定需要实例化的Java类,包括类的一组初始化值,通过加载读取配置文件,用spring提供的方法(getbean())获取进行初始化的实例对象。

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

DI(依赖注入)其实就是IOC的一种类型,还有一种是DL(Dependency Lookup依赖查找)。  DL由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式反转了。

理解Spring中的IoC和DI - 超超不会飞 - 博客园 (cnblogs.com)

容器所为SpringIOC的核心它主要有两种:

BeanFactory:BeanFactory为IOC容器提供了基础功能,Spring文档中提到,当前该类仅仅是为了向后兼容老的版本,除非你有更好的原因否则就应该使用第二种容器。

ApplicationContext:通过API文档可以知道,ApplicationContext是BeanFactory的子接口,并且从文档中也可以看到ApplicaionContext除了包含有BeanFactory的所有功能还支持了更多的功能。

ApplicationContext的实现有四种方式:

FileSystemXmlApplicationContext:加载配置文件的时候采用的是项目的路径。

ClassPathXmlApplicationContext:加载配置文件的时候根据ClassPath位置。

XmlWebApplicationContext:在Web环境下初始化监听器的时候会加载该类。

AnnotationConfigApplicationContext:根据注解的方式启动Spring 容器。
 

8.将一个类声明为bean的注解

一般使用@autowired注解自动装配bean,要想把类标识成可用于@autowired注解自动装配的bean的类,可用以下:

(1)@component,通用注解

(2)@repository ,对应持久层即Dao层

(3)@service,对应服务层,需要用到Dao层

(4)@controller对应MVC的控制层,接受用户请求并调用service层返回的数据给前端页面。

9.bean的生命周期

 

 

 

 10,spring的内部bean

只有将bean用作另一个bean的属性时,才能将bean声明为内部bean。

 不谈扩展性,spring开始时只有两个流程,对象的实例化和属性填充。

对象的实例化就是使用类构造器进行创建对象。而一个类中可能有很多的构造器,那么怎么知道使用哪个构造器进行实例化对象呢?

在实例化前,需要确定候选的构造器,即构造器推断。

构造器推断

功能描述:寻找beanClass中所有符合候选条件的构造器 。

负责角色:AutowiredAnnotationBeanPostProcessor

候选条件:构造器上加了@Autowired注解,@value和@Inject注解,也可以用setter

推断流程:

1、获取beanClass中的所有构造器进行遍历,使用的是beanclass.getDeclaredConstructors()判断构造器上是否标识@Autowired注解,是则将构造器添加到候选构造器集合中

2、并进一步判断Autowired注解中required属性是否为true(默认为true),是则表示该beanClass已存在指定实例化的构造器,不可再有其他加了@Autowired注解的构造器,如果有则抛出异常。

3、如果Autowired注解中required属性为false,则可继续添加其他@Autowired(required=false)标识的构造器

4、如果候选构造器集合不为空(有Autowired标识的构造器),并且beanClass中还有个空构造器,那么同样将空构造器也加入候选构造器集合中。

5、如果候选构造器集合为空,但是beanClass中只有一个构造器且该构造器有参,那么将该构造器加入候选构造器集合中。

 以上判断条件很多,但始终是围绕这一个逻辑:这个beanClass中有没有被Autowired标识的构造器,有的话required是true还是false,如果是true, 那其他的构造器都不要了。如果是false,那想加多少个构造器就加多少个。

那要是没有autowired标识的构造器呢?

 都要有兜底的,这里就是看beanclass中是不是只有一个构造器且是有参的。

那如果只有无参,那就是没有候选构造器,那spring会在没有候选构造器时默认使用无参构造器。

如果有很多个构造器,spring也不知道用哪个,同样进入兜底使用无参构造器(不抛出异常)。

得到构造器,下面对对象进行实例化

功能描述:根据候选构造器集合中的构造器优先级对beanclass进行实例化。

负责角色:ConstructorResolver

需要关注:1.构造器的优先级是怎样?2.如果有多个构造器,但是部分构造器需要的bean不存在于spring容器中会发生说明?也就是出现异常怎么处理?

1.构造器优先级怎么样的?

答:多个构造器称为构造器的重载,重载的方式有两种:参数的数量不同,参数的类型不同。

在Spring中,优先级则是由构造器的修饰符(public or private)和参数的数量决定。

规则如下:

(1)、public修饰的构造器  > private修饰的构造器(非public)

(2)、修饰符相同的情况下参数数量更多的优先

/ 如果一个是public,一个不是,那么public优先
int result = Boolean.compare(Modifier.isPublic(e2.getModifiers()), Modifier.isPublic(e1.getModifiers()));
// 都是public,参数多的优先
return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount());

2.spring出现异常怎么处理?

这一部分的具体过程如下:

(1)、将根据优先级规则排序好的构造器进行遍历

(2)、逐个进行尝试查找构造器中的需要的bean是否都在Spring容器中,如果成功找到将该构造器标记为有效构造器,并立即退出遍历

(3)、否则记录异常继续尝试使用下一个构造器

(4)、当所有构造器都遍历完毕仍未找到有效的构造器,抛出记录的异常

(5)、使用有效构造器进行实例化

 到这里,beanClass实例化了一个bean,接下来需要做的便是对bean进行赋值,但我们知道,Spring中可以进行赋值的对象不仅有通过@Autowired标识的属性,还可以是@Value,@Resource,@Inject等等。

为此,Spring为了达到可扩展性,将获取被注解标识的属性的过程与实际赋值的过程进行了分离。

该过程在Spring中被称为处理beanDefinition

处理beanDefinition

功能描述:处理beanDefinition的元数据信息

负责角色:

1、AutowiredAnnotationBeanPostProcessor:处理@Autowird,@Value,@Inject注解

2、CommonAnnotationBeanPostProcessor:处理@PostConstruct,@PreDestroy,@Resource注解

这两个后置处理器的处理过程十分类似,  我们以AutowiredAnnotationBeanPostProcessor为例:

1、遍历beanClass中的所有FieldMethod(java中统称为Member

2、判断Member是否标识@Autowird,@Value,@Inject注解

3、是则将该Member保存,封装到一个叫做InjectionMetadata的类中

4、判断Member是否已经被解析过,比如一个Member同时标识了@Autowired@Resource注解,那么这个Member就会被这两个后置处理器都处理一遍,就会造成重复保存

5、如果没被解析过就将该Member放置到已检查的元素集合中,用于后续填充属性时从这里直接拿到所有要注入的Member

 现在,beanclass中可注入属性都找到了,接下来就要进行属性填充。

属性填充

功能:对bean中需要自动装配的属性进行填充

角色:

1、AutowiredAnnotationBeanPostProcessor

2、CommonAnnotationBeanPostProcessor

我们同样以AutowiredAnnotationBeanPostProcessor为例

1、使用beanName为key,从缓存中取出InjectionMetadata

2、遍历InjectionMetadata中的checkedElements集合

3、取出Element中的Member,根据Member的类型在Spring中获取Bean

4、使用反射将获取到的Bean设值到属性中

在Spring中,Bean填充属性之后还可以做一些初始化的逻辑,比如Spring的线程池ThreadPoolTaskExecutor在填充属性之后的创建线程池逻辑,RedisTemplate的设置默认值。

Spring的初始化逻辑共分为4个部分:

1、invokeAwareMethods:调用实现特定Aware接口的方法,主要是得到bean所在容器名字和classcloader,beanfactory

2、applyBeanPostProcessorsBeforeInitialization:初始化前的处理

3、invokeInitMethods:调用初始化方法

4、applyBeanPostProcessorsAfterInitialization:初始化后的处理

初始化逻辑

Spring的初始化逻辑共分为4个部分:

1、invokeAwareMethods:调用实现特定Aware接口的方法

2、applyBeanPostProcessorsBeforeInitialization:初始化前的处理

3、invokeInitMethods:调用初始化方法

4、applyBeanPostProcessorsAfterInitialization:初始化后的处理

初始化前的处理

功能:调用初始化方法前的一些操作

角色:

1、InitDestroyAnnotationBeanPostProcessor:处理@PostContrust注解

2、ApplicationContextAwareProcessor:处理一系列Aware接口的回调方法

这一步骤的功能没有太大的关联性,完全按照使用者自己的意愿决定想在初始化方法前做些什么,我们一个一个来过。

1.InitDestroyAnnotationBeanPostProcessor

这里的逻辑与属性填充过程非常相似,属性填充过程是取出自动装配相关的InjectionMetadata进行处理,而这一步则是取@PostContrust相关的Metadata进行处理,这个Metadata同样也是在处理BeanDefinition过程解析缓存的

初始化方法

在Spring中的初始化方法有两种

1、实现InitializingBean接口的afterPropertiesSet方法

2、@Bean注解中的initMethod属性

调用顺序是先调用afterPropertiesSetinitMethod

1、判断Bean是否实现InitializingBean接口

2、是则将Bean强转成InitializingBean,调用afterPropertiesSet方法

3、判断BeanDefinition中是否有initMethod

4、是则找到对应的initMethod,通过反射进行调用

 

 初始化后的处理

在Spring的内置的后置处理器中,该步骤只有ApplicationListenerDetector有相应处理逻辑:将实现了ApplicationListener接口的bean添加到事件监听器列表中

如果使用了Aop相关功能,则会使用到AbstractAutoProxyCreator,进行创建代理对象。

ApplicationListenerDetector的流程如下

1、判断Bean是否是个ApplicationListener

2、是则将bean存放到applicationContext的监听器列表中

 

11.spring装配

当bean在容器中组合在一起,称为bean装配。spring容器需要知道需要什么bean以及如何使用依赖将bean绑定起来。

通过检查beanfactory,不同模式:

(1)no;(2)byName;(3)byType(4)构造函数,有大量参数;(5)autodetect,尝试通过构造函数使用auto wired装配,不能就尝试通过bytype自动装配。

 12.如果出现同名bean

(1)同一配置文件的,以最上面定义为准

(2)不同配置文件的,以后解析的为准,覆盖

(3)同文件的@bean和@componentscan,以bean生效;

13.spring中单例bean的线程安全问题

线程安全问题都是由全局变量及静态变量引起的。如果只读没问题,但是多线程同时写有问题。

无状态和有状态bean

有状态就是有实例变量的对象,可以保存数据,是非线程安全的。适用于prototype原型模式。

无状态就是一次操作,不能保存数据。是线程安全的。适用于单例模式。

spring适用threadlocal解决线程安全问题。

@value($())是在XML中找Key,@value(“”)是直接指定字符串,

IOC的理解:

软件系统在没有引⼊ IOC 容器之前,对象 A 依赖于对象 B ,那么对象 A 在初始化或者运⾏到某⼀点的时候,⾃⼰ 必须主动去创建对象 B 或者使⽤已经创建的对象 B 。⽆论是创建还是使⽤对象 B ,控制权都在⾃⼰⼿上。
软件系统在引⼊ IOC 容器之后,这种情形就完全改变了,由于 IOC 容器的加⼊,对象 A 与对象 B 之间失去了直接 联系,所以,当对象 A 运⾏到需要对象 B 的时候, IOC 容器会主动创建⼀个对象 B 注⼊到对象 A 需要的地⽅。 通过前后的对⽐,我们不难看出来:对象 A 获得依赖对象 B 的过程,由主动⾏为变为了被动⾏为,控制权颠倒过 来了,这就是“ 控制反转 这个名称的由来。

autowire:
    Spring有个自动装配的机制会帮我们自动的把userDaoImpl注入到userServiceImpl中,通过配置autowire可以实现。直接举个栗子。可以看到我们在配置文件中删掉了userDaoImpl,但是Spring一样会帮我们成功注入。但是要注意的是,如果我们选择的是byType,那么对于存在两个相同Class的userDaoImpl Spring会不知道要注入哪一个,因此会抛出异常。
        针对上述的问题,Spring提供了两个方法来解决。第一种如果你在id为userDaoImpl2上面添加了autowire-candidate,那么就说明如果自动装配的时候出现冲突,就会忽略当前这个bean即userDaoImpl2。而primary则是反过来,它表示的是一种优先级,如果遇到冲突情况优先使用带有该属性的bean。
 

@Autowired,@Resource;
    @Autowired跟@Resource都可以作用在构造方法,sette方法,以及属性值上面注入。它们的区别在于:

Autowired是Spring的规范,而Resource是java的规范;
Autowired默认按类型匹配但是可以搭配@Qualifier来指定名称,而Resource默认按名称;
   
Configuration表明当前这个类对应一个XML文件,另外在Junit中我们不再使用ClassPathXMLApplicationContext,而是改用了AnnotationConfigApplicationContext。因为我们不再通过XML的方式来获取Bean。

Spring在代码中获取bean的几种方式

方法一:在初始化时保存ApplicationContext对象 
方法二:通过Spring提供的utils类获取ApplicationContext对象 
方法三:继承自抽象类ApplicationObjectSupport 
方法四:继承自抽象类WebApplicationObjectSupport 
方法五:实现接口ApplicationContextAware 
方法六:通过Spring提供的ContextLoader

springboot bean扫描路径

1:默认扫描启动类所在路径下全部的beanservice

2:能够在启动类中添加注解,手动指定扫描路径:vi

@ComponentScan(basePackages = {"com.xxx.service1.*","com.xxx.service2.**"})

1.@Component: 注解表明一个类会作为组件类,并告知Spring要为这个类创建bean,使用          @Component注解在一个类上,表示将此类标记为Spring容器中的一个Bean。(相当于创建对象)

2.@Bean是将组件注册到Bean,让IOC容器知道这个组件存在。(相当于创建对象) 

一定要配合@configuration使用

@Component 作用于类,@Bean作用于方法。

​​​​​​​如果你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component注解的,因此就不能使用自动化装配的方案了,但是我们可以使用@Bean,当然也可以使用XML配置。

3.@Autowire:是组件和组件相互调用的时候,自动从ioc中取出来需要用的组件。(调用对象)

比如Service,Controller,Dao的关系,这三个组件都分别加上了注册的注解:@Service,@Controller,@Component,ioc中已经有了注册信息,但是Service要用到Dao操作数据,所以在Service中的Dao头上就要用@Autowired来给Dao自动赋值,来供Service用,同理,Controller中也要用到Service,那么就要在Service上边加上@Autowired 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值