Spring核心组件和启动流程

1.Spring常用七大模块

  • Spring Core:框架的最基础部分,提供 IoC 和DI 服务,对 bean 进行管理。
  • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、EJB、电子邮件、国际化、定时任务等)
  • Spring AOP:集成了所有AOP功能
  • Spring Web:提供了基础的 Web 开发的上下文信息,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器
  • Spring MVC:提供面向Web应用的Model-View-Controller实现
  • Spring DAO:对JDBC的抽象封装,还提供了声明性事务管理方法
  • Spring ORM:对现有的ORM框架的支持,提供了JPA、JDO、Hibernate、MyBatis 等ORM映射层

2.Spring常用注解

Bean组件申明

  • @Controller:控制层
  • @RestController:相当于@Controller + @ResponseBody
  • @Service:业务层
  • @Repository:数据访问层
  • @Component:泛指各种组件
  • @Bean:标注的方法产生一个Bean对象,然后这个Bean对象交给Spring管理

Bean注入

  • @Autowired:可以作用在变量、setter方法、构造函数上;默认按照类型匹配,如果需要按照名称匹配需要和@Qualifier一起使用
  • @Resource:按照名称匹配

Web接口

  • @RequestMapping
  • @PathVariable
  • @RequestParam
  • @Requestheader
  • @CookieValue
  • @ResponseBody

AOP

  • @Aspect:定义切面类,将被标识的类作为一个切面bean
  • @Pointcut:切点
  • @Around:环绕增强
  • @Before:前置增强
  • @AfterReturning:后置增强—方法正常退出时执行
  • @AfterThrowing:后置增强—方法异常执行
  • @After:后置增强—增强anyway

3. Spring IoC

IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器)

将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。

Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。

4. Spring AOP

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

静态代理

AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

动态代理

Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  • JDK动态代理:只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起。
    InvocationHandler的invoke(Object proxy,Method method,Object[] args)
    • proxy:是最终生成的代理对象
    • method:是被代理目标实例的某个具体方法;
    • args:是被代理目标实例某个方法的具体入参, 在方法反射调用时使用
  • CGLIB动态代理:如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

静态代理与动态代理区别?

生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

AOP 主要应用场景有:

  • Authentication 权限
  • Caching 缓存
  • Context passing 内容传递
  • Error handling 错误处理
  • Lazy loading 懒加载
  • Debugging 调试
  • logging, tracing, profiling and monitoring 记录跟踪 优化 校准
  • Performance optimization 性能优化
  • Persistence 持久化
  • Resource pooling 资源池
  • Synchronization 同步
  • Transactions 事务

4.过滤器(Filter)

主要作用是过滤字符编码、做一些业务逻辑判断,主要用于对用户请求进行预处理,同时也可进行逻辑判断。Filter在请求进入servlet容器执行service()方法之前就会经过filter过滤,不像Intreceptor一样依赖于springmvc框架,只需要依赖于servlet。Filter启动是随WEB应用的启动而启动,只需要初始化一次,以后都可以进行拦截。Filter有如下几个种类:

  • 用户授权Filter:检查用户请求,根据请求过滤用户非法请求
  • 日志Filter:记录某些特殊的用户请求
  • 解码Filter:对非标准编码的请求解码

5.拦截器(Interceptor)

主要作用是拦截用户请求,进行处理。比如判断用户登录情况、权限验证,只要针对Controller请求进行处理,是通过HandlerInterceptor

Interceptor分两种情况,一种是对会话的拦截,实现spring的HandlerInterceptor接口并注册到mvc的拦截队列中,其中**preHandle()**方法在调用Handler之前进行拦截,**postHandle()**方法在视图渲染之前调用,**afterCompletion()**方法在返回相应之前执行;另一种是对方法的拦截,需要使用@Aspect注解,在每次调用指定方法的前、后进行拦截。

Filter和Interceptor的区别

  • Filter是基于函数回调(doFilter()方法)的,而Interceptor则是基于Java反射的(AOP思想)
  • Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器
  • Filter对几乎所有的请求起作用,而Interceptor只能对action请求起作用
  • Interceptor可以访问Action的上下文,值栈里的对象,而Filter不能
  • 在action的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次
  • Filter在过滤是只能对request和response进行操作,而interceptor可以对request、response、handler、modelAndView、exception进行操作

HandlerInterceptor

HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。

实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。

这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承他之后只需要重写必要的方法。

MethodInterceptor

MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是controller中的方法。实现MethodInterceptor 拦截器大致也分为两种,一种是实现MethodInterceptor接口,另一种利用AspectJ的注解或配置。

6.Spring容器启动流程(初始化+刷新)

  • BeanFactory:spring底层容器,定义了最基本的容器功能,注意区分FactoryBean
  • ApplicationContext:扩展于BeanFactory,拥有更丰富的功能。例如:添加事件发布机制、父子级容器,一般都是直接使用它
  • Resource:bean配置文件,一般为xml文件。可以理解为保存bean信息的文件
  • BeanDefinition:定义了bean的基本信息,根据它来创造bean

1--初始化(new ApplicationContext)

1.初始化Spring容器,javaConfig使用AnnotationConfigApplicationContext,xml使用ClassPathXmlApplicationContext

  • 1.实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象
  • 2.1实例化BeanDefinitionReader注解配置读取器,用于对特定注解的类进行读取转化成 BeanDefinition 对象。
  • 2.2然后注册内置的BeanPostProcessor的BeanDefinition到容器中(ConfigurationClassPostProcessor用来完成bean的扫描与注入工作、AutowiredAnnotationBeanPostProcessor用来完成@AutoWired自动注入)
  • 3.实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象

2.(解析配置类)将配置类的BeanDefinition注册到容器中(向Map中注册 Map<name,beandefinition>)

2--刷新(applicationContext.refresh)

1.刷新前的预处理,以及向容器中添加一些组件

2.执行beanfactory的后置处理

3.initMessageSource()初始化 MessageSource 组件(做国际化功能;消息绑定,消息解析)

4.initApplicationEventMulticaster()初始化事件派发器,在注册监听器时会用到

5.onRefresh()留给子容器(子类),子类重写这个方法,在容器刷新的时候可以自定义逻辑,web 场景下会使用

6.registerListeners()注册监听器,派发之前步骤产生的一些事件(可能没有)

7.finishBeanFactoryInitialization()初始化所有非懒加载的单例bean

  • 1.如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使用该Bean的时候,直接从这个缓存中取 
  • 2.如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进行实例化 
  • 3.其它情况该Bean的实例化是在第一次使用该Bean的时候进行实例化 

8.finishRefresh()发布容器刷新完成事件

7.Bean的作用域(@Scope)

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的,对单例设计模式的应用。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

单例 Bean 的线程安全问题

单例 bean 存在线程安全问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。常见的有两种解决办法:

  • 在 bean 中尽量避免定义可变的成员变量。
  • 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

不过,大部分 bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, bean 是线程安全的。

Bean的生命周期

  1. 实例化Bean: Ioc容器通过获取BeanDefinition对象中的信息进行实例化,实例化对象被包装在BeanWrapper对象中
  2. populateBean设置对象属性/依赖注入(DI):通过BeanWrapper提供的设置属性的接口完成属性依赖注入;
  3. 初始化1:若实现了Aware接口,则进行相应Aware信息注入(BeanFactoryAware可以用这个方式来获取其它 Bean)
  4. 初始化2:调用前置BeanPostProcessor处理器。如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法,init之后执行postProcessAfterInitialization()方法;
  5. 初始化3:InitializingBean和init-method:如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法;如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法
  6. 初始化4:调用后置BeanPostProcessor处理器(通过AnnotationAwareAspectJAutoProxyCreator后置处理器来完成aop,返回代理bean)
  7. 使用
  8. DisposableBean和destroy-method:bean的销毁,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法;如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

依赖注入流程?

过程在Ioc初始化后,依赖注入的过程是第一次向IoC容器索要Bean时触发

  • 如果lazy-init=true,会在第一次getBean的时候才初始化bean, =false会在容器启动的时候直接初始化(singleton bean);
  • 调用BeanFactory.getBean()生成bean;
  • 生成bean过程运用装饰器模式产生的bean都是beanWrapper(bean的增强);

怎么检测Bean是否存在循环依赖?

Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

Spring如何解决Bean的循环依赖?

原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断抛出异常)

  • singletonObjects:第一级缓存,里面放置的是初始化好的单例对象;
  • earlySingletonObjects:第二级缓存,里面存放的是未初始化的单例对象;
  • singletonFactories:第三级缓存,里面存放的是获取未初始化对象的工厂;

Spring通过三级缓存(3个map)解决了循环依赖,当A、B两个类发生循环引用时,

0.getBean()获取A,三级缓存中没有,则开始创建A,首先通过反射实例化A

1.在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

2.当A进行初始化属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:

1)先获取到三级缓存中的工厂,调用对象工厂的getObject方法来获取到对象A,得到对象A后将其注入到B中;

2)将三级缓存中的工厂删除,并将对象A放入二级缓存;

3.紧接着B会走完它的生命周期流程,包括初始化、后置处理器等;

4.当B创建完后,会将B注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

1.如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要立刻完成AOP代理,再进行初始化依赖注入。

2.而Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

3.当出现循环依赖时,B会从三级缓存中获取A的beanFactory调用getObject获取A,获取时检查A是否有SmartInstantiationAwareBeanPostProcessor后置处理器,有则调用它的getEarlyBeanReference获取代理对象(获取过的会在处理器中缓存,当初始化完成执行后置处理器时不会重复创建)

Spring在创建Bean的过程中分为三步

  1. 实例化(反射创建实例,然后通过实例创建BeanWrapper,对应方法:AbstractAutowireCapableBeanFactory的createBeanInstance方法),然后添加创建一个ObjectFactory来返回该实例,并将factory放入singletonFactories中;
  2. 属性注入(为实例化出来的对象填充属性),对应方法:AbstractAutowireCapableBeanFactory的populateBean方法
  3. 初始化(执行aware接口中的方法,初始化方法,完成AOP代理),对应方法:AbstractAutowireCapableBeanFactory的initializeBean

8. Spring 事务

Spring 管理事务的方式有几种?

  • 编程式事务 : 在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
  • 声明式事务 : 在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

Spring 事务中哪几种事务传播行为?

事务传播行为是为了解决业务层方法之间互相调用的事务问题

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

正确的事务传播行为可能的值如下:

1.TransactionDefinition.PROPAGATION_REQUIRED

使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

2.TransactionDefinition.PROPAGATION_REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

3.TransactionDefinition.PROPAGATION_NESTED

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

4.TransactionDefinition.PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

这个使用的很少。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚:

  1. TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

Spring 事务中的隔离级别有哪几种?

(执行事务时会对connection设置此次事务隔离级别 https://www.csdn.net/tags/NtjaUg1sNjczMDEtYmxvZwO0O0OO0O0O.html )

  • TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

@Transactional(rollbackFor = Exception.class)注解了解吗?

当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性;

同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

在 @Transactional 注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。

@Transactional 注解失效

若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。

  • @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
  • 被 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  • 底层使用的数据库必须支持事务机制,否则不生效;
  • 正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败;

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值