Spring思维导图(知识点总结,含面试题整理)
2021-04-11 21:53:58 24 举报
AI智能生成
理解Spring框架的设计理念,了解Spring的模块,掌握所应用的设计模式。 深入理解Spring的控制反转与AOP,Spring的Beans Spring事务管理知识汇总 公众号【小张日拱一卒】,回复ssm获取视频文档资源。
作者其他创作
大纲/内容
Spring概述
Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
为了降低Java开发的复杂性,Spring采取了4种关键策略
基于POJO(plain ordinary java object)的轻量级和最小侵入性编程
通过依赖注入和面向接口实现松耦合
基于切面和惯例进行声明式编程
通过切面和模板减少样板式代码
Spring框架的设计目标,设计理念,和核心是什么
Spring设计目标
Spring为开发者提供一个一站式轻量级应用开发平台
Spring设计理念
在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法
Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦
Spring框架的核心
IoC容器和AOP模块。通过IoC容器管理POJO对象(之前自己管理,自己new对象。现在扔给Spring管理,用时直接从Spring中获取)
以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务
以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件
Spring的优缺点是什么?
优点
非侵入性
对原来技术不影响
依赖注入DI(Dependency Injection) 、反转控制IOC(inversion of control)
IOC是思想,DI:IOC的实现
优点见IOC篇
面向切面编程AOP
AOP 对OOP进行补充
实现对程序进行权限拦截、运行监控等功能
容器
Spring是一个容器
Spring IOC 容器负责创建对象,管理对象(通过依赖注入DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
只用new的方法
复杂项目需要1000个对象,需开辟1000个内存空间 同时不能保证他们都在使用,引入单例,需要自己去造
有了Spring
容器,管理多个对象=Spring的组件 ,就可以管理对象的生命周期 ,使用的时候直接去调用。
Tomcat:web容器,可以管理servlet的生命周期
组件化
Spring实现了使用简单的组件配置组合成一个复杂的应用
在 Spring 中可以使用XML和Java注解组合这些对象
降低耦合,一个对象存在与否与另一个对象联系不大
项目:各个类,由类产生对象,由对象调用方法最终实现功能。
Spring可以把项目中所有的类进行管理,这些对象称为组件,组件化
Spring 应用程序有哪些不同组件?
接口 - 定义功能
Bean 类 - 它包含属性,setter 和 getter 方法,函数等
Bean 配置文件 - 包含类的信息以及如何配置它们
Spring 面向切面编程(AOP) - 提供面向切面编程的功能
用户程序 - 它使用接口
一站式
在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库
使用 Spring 有哪些方式?
作为一个成熟的 Spring Web 应用程序
作为第三方 Web 框架,使用 Spring Frameworks 中间层
作为企业级 Java Bean,它可以包装现有的 POJO(Plain Old Java Objects)
用于远程使用
声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程
方便程序的测试
Spring对Junit4支持,可以通过注解方便的测试Spring程序
降低JavaEE API的使用难度
Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
缺点
Spring明明一个很轻量级的框架,却给人感觉大而全
Spring依赖反射,反射影响性能
Spring由哪些模块组成?
核心容器(Core Container)
控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)
Spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
详细讲解一下核心容器(spring context应用上下文) 模块
提供spring 框架的基础功能,BeanFactory 是任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。
Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。
最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。
该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。
该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
Spring框架中有哪些不同类型的事件
上下文更新事件(ContextRefreshedEvent)
在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发
上下文开始事件(ContextStartedEvent)
当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件
上下文停止事件(ContextStoppedEvent)
当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件
上下文关闭事件(ContextClosedEvent)
当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁
请求处理事件(RequestHandledEvent)
在Web应用中,当一个http请求(request)结束触发该事件。
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知
AOP(Aspect Oriented Programming)和设备支持(Instrmentation)
AOP也是依赖于IOC
spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等
消息(Messaging)
数据访问与集成(Data Access/Integeration)
从下至上。ORM对象关系映射 用在持久层框架中,从数据库中查询出来的数据自动赋值给对象。操作数据库像操作实体类一样简单
spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC
Web
spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext
Test
spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试
Spring 框架中都用到了哪些设计模式?
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
单例模式:Bean默认为单例模式
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,
如Spring中listener的实现:ApplicationListener
如Spring中listener的实现:ApplicationListener
包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
Spring控制反转(IOC)
什么是Spring IOC 容器?
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
Spring IOC 容器负责创建对象,管理对象(通过依赖注入DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
IOC 管理对象 配置到配置文件中
理解内容
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,
在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式
反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,
开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。
这种行为也称为查找的被动形式。
开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。
这种行为也称为查找的被动形式。
传统方式: 我想吃饭 我需要买菜做饭
反转控制: 我想吃饭 饭来张口(获取对象) (订外卖)
不知道Spring是如何创建对象的,我们只关心,在用对象的时候能直接拿来使用。
IOC思想:自己对对象的控制权交给容器,让容器自己去管理
控制反转(IoC)有什么作用
管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的
解耦,由容器去维护具体的对象
托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
IOC的优点是什么?
IOC 或 依赖注入把应用的代码量降到最低
它使应用容易测试,单元测试不再需要单例和JNDI查找机制。Java Naming and Directory Interface,Java命名和目录接口
最小的代价和最小的侵入性使松散耦合得以实现
IOC容器支持加载服务时的饿汉式初始化和懒加载
Spring IoC 的实现机制
前提: Spring中有IOC思想, IOC思想必须基于 IOC容器(ApplicationContext接口)来完成, 而IOC容器在最底层实质上就是一个对象工厂。
工厂模式加反射机制
在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
Spring提供了IOC容器的两种实现方式
BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
ApplicationContext:BeanFactory的子接口(接口越向下,功能越丰富),提供了更多高级特性。
面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
类关系图
ApplicationContext
ApplicationContext 是一个接口 不能直接实例化,通过接口的实现类来进行实例化。
ApplicationContext通常的实现是什么?
ClassPathXmlApplicationContext
此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置
对应类路径下的XML格式的配置文件(相对路径)
可以看到它的多个重载的方法
FileSystemXmlApplicationContext
此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数
对应文件系统中的XML格式的配置文件(绝对路径)
WebXmlApplicationContext
此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
初始化容器
在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。
ConfigurableApplicationContext
1)是ApplicationContext的子接口,包含一些扩展方法
2)refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。
WebApplicationContext(SpringMVC)
专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
Spring 的 IoC支持哪些功能
依赖注入
最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象
依赖检查
自动装配
支持集合
指定初始化方法和销毁方法
支持回调某些方法(但是需要实现 Spring 接口,略有侵入)
ApplicationContext和BeanFactory的区别
可以从ApplicationContext继承的这几个接口入手,除去BeanFactory相关的两个接口就是ApplicationContext独有的功能
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolve
MessageSource, ApplicationEventPublisher, ResourcePatternResolve
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
依赖关系
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能
继承MessageSource,因此支持国际化
统一的资源文件访问方式
提供在监听器中注册bean的事件
同时加载多个配置文件
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
加载方式
BeanFactroy采用的是延迟加载(懒加载)形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢
创建方式
BeanFactory通常以编程的方式被创建
ApplicationContext还能以声明的方式创建,如使用ContextLoader
注册方式
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用
两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
Spring 如何设计容器的,BeanFactory和ApplicationContext的关系详解
BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取ResourcePatternResolve,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。
为了更直观的展示 “低级容器” 和 “高级容器” 的关系,这里通过常用的 ClassPathXmlApplicationContext 类来展示整个容器的层级 UML 关系
最上面的是 BeanFactory,下面的 3 个绿色的,都是功能扩展接口
看下面的隶属 ApplicationContext 粉红色的 “高级容器”,依赖着 “低级容器”,这里说的是依赖,不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer 模式)
通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦
左边灰色区域的是 “低级容器”, 只负载加载 Bean,获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置,生命周期事件回调等
小结
IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤
加载配置文件,解析成 BeanDefinition 放在 Map 里
调用 getBean 时,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 ,完成依赖注入
至于高级容器 ApplicationContext,他包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。
同时其作为高级容器,包含了太多的功能。一句话,他不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。
同时其作为高级容器,包含了太多的功能。一句话,他不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。
什么是Spring的依赖注入?
控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找
IOC是思想,DI:IOC的实现
依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
如何理解?
例如:bean标签中name=“id”value=“888”value就是对name的注入
一个Bean看作new的一个对象
解释
依赖:Car – 发动机/方向盘对象 可以让Spring来维护关系
注入: 赋值
依赖于谁就为谁赋值
依赖注入的基本原则
应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。
依赖注入有什么优势
依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口(已弃用),使容器可以在初始化时组装对象的依赖关系
其与依赖查找方式相比,主要优势为
查找定位操作与应用代码完全无关
不依赖于容器的API,可以很容易地在任何容器以外使用应用对象
不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器
有哪些不同类型的依赖注入实现方式?
通过bean的构造器赋值
构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖
没有部分注入
不会覆盖 setter 属性
任意修改都会创建一个新实例
适用于设置很多属性
使用建议:构造器参数实现强制依赖
Spring自动匹配合适的构造器
通过索引值指定参数位置(一般结合type使用)
<constructor-arg value="90" index="2" type="java.lang.Double"/>
通过类型区分重载的构造器
通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。
通过bean的setXxx()方法赋值
Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入
有部分注入
会覆盖 setter 属性
任意修改不会创建一个新实例
适用于设置少量属性
使用建议:setter方法实现可选依赖
Property标签通过set方法进行赋值 进行注入(用的最多)
<property name="id" value="10010"/>
bean标签内添加属性
name:属性名
value:属性值
依赖与反射,反射必须要有无参构造器
FactoryBean
工厂模式,不在乎怎么创建的对象,直接从工厂里取出需要的对象;工厂来创建对象
带泛型的,当前工厂创建出来的对象类型,FactoryBean<Car> (接口)
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。
Spring Beans
什么是Spring beans?
Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 的形式定义。
resources/.xml
<bean id="person" class="com.atguigu.spring.mod.Person" scope="singleton"></bean>
定义spring所管理的一个对象
id:该对象的唯一标示,注意:不能重复,在通过类型获取bean的过程中可以不设置 Peason.class
通过类型获取bean
Person person = ac.getBean("person", Person.class);
不需要强转也可以唯一获取
class:此对象所属类的全限定名 指明对象 是属于哪个类的
class="com.atguigu.spring.mod.Person",依赖与反射,反射必须要有无参构造器
scope:设置bean的作用域
Spring单例模式只需要一个属性设置,而不需要自己去设计单例模式,构造方法私有化,提供一个对外的公共访问点。
singleton:单例,唯一实例(默认)
容器初始化时,Spring里的bean会被创建
使用无参构造器,多次调用只调用了一次构造方法
Spring框架中的单例bean是线程安全的吗?
不是,Spring框架中的单例bean不是线程安全的。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。
实际上大部分时候 spring bean 无状态的(比如 dao 类),所以某种程度上来说 bean 也是安全的,
但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了
但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了
有状态就是有数据存储功能
当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题
无状态就是不会保存数据。
解决方案
改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
Spring如何处理线程并发问题?
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
prototype:原型,多例;
多例即原型的bean,会在使用时创建
使用无参构造器,有几次调用,就有几次构造方法
使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销
request:每一次HTTP请求都会产生一个新的bean(对象),该bean仅在当前HTTP request内有效。
session:每一次HTTP请求都会产生一个新的bean(对象),该bean仅在当前HTTP session内有效。
说说bean的生命周期
Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
Spring Bean的生命周期只有这四个阶段
doCreate()方法顺序调用以下三个方法
createBeanInstance() -> 实例化
populateBean() -> 属性赋值
实例化和属性赋值对应构造方法和setter方法的注入
initializeBean() -> 初始化
初始化和销毁是用户能自定义扩展的两个阶段
销毁 Destruction
在这四步之间穿插的各种扩展点
常用扩展点
第一大类:影响多个Bean的接口
实现了这些接口的Bean会切入到多个Bean的生命周期中。
正因为如此,这些接口的功能非常强大,Spring内部扩展也经常使用这些接口,
例如自动注入以及AOP的实现都和他们有关。
正因为如此,这些接口的功能非常强大,Spring内部扩展也经常使用这些接口,
例如自动注入以及AOP的实现都和他们有关。
InstantiationAwareBeanPostProcessor
作用于实例化阶段的前后
postProcessBeforeInstantiation调用点
postProcessBeforeInstantiation在doCreateBean之前调用
作用于实例化之前
该方法的返回值会替换原本的Bean作为代理
这也是Aop等功能实现的关键点
postProcessAfterInstantiation调用点
该方法在属性赋值方法populateBean内,但是在真正执行赋值操作之前
其返回值为boolean,返回false时可以阻断属性赋值阶段
继承了BeanPostProcessor接口
BeanPostProcessor
作用于初始化阶段的前后
关于BeanPostProcessor执行阶段的源码穿插在下文Aware接口的调用时机分析中,因为部分Aware功能的就是通过他实现的
第二大类:只调用一次的接口
Aware类型的接口
Aware类型的接口的作用就是让我们能够拿到Spring容器中的一些资源。
基本都能够见名知意,Aware之前的名字就是可以拿到什么资源,例如BeanNameAware可以拿到BeanName,以此类推。
基本都能够见名知意,Aware之前的名字就是可以拿到什么资源,例如BeanNameAware可以拿到BeanName,以此类推。
调用时机需要注意:所有的Aware方法都是在初始化阶段之前调用的!
Aware接口具体可以分为两组
Aware Group1
BeanNameAware
BeanClassLoaderAware
BeanFactoryAware
Aware Group2
EnvironmentAware
EmbeddedValueResolverAware
实现该接口能够获取Spring EL解析器,用户的自定义注解需要支持spel表达式的时候可以使用,非常方便
ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware)
其返回值实质上都是当前的ApplicationContext对象,因为ApplicationContext是一个复合接口
(涉及到另一道面试题)ApplicationContext和BeanFactory的区别
分类依据:排列顺序同样也是Aware接口的执行顺序
Aware调用时机源码分析
initializeBean() -> 初始化源码中
说明了Aware都是在初始化阶段之前调用的
invokeAwareMethods(beanName, bean);
Aware Group1中的三个Bean开头的Aware
在代码中直接调用的
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
这里调用的是Group2中的几个Aware
而实质上这里就是前面所说的BeanPostProcessor的调用点
也就是说与Group1中的Aware不同,这里是通过BeanPostProcessor(ApplicationContextAwareProcessor)实现的
invokeInitMethods(beanName, wrappedBean, mbd);
initializeBean() -> 初始化调用点
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
BeanPostProcessor的另一个调用点
作用于初始化阶段之后
生命周期接口
实例化和属性赋值都是Spring帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段
initializeBean 对应生命周期的初始化阶段
invokeInitMethods(beanName, wrappedBean, mbd);
注意:Aware方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用Aware接口获取的资源,这也是我们自定义扩展Spring的常用方式。
除了实现InitializingBean接口之外还能通过注解或者xml配置的方式指定初始化方法
DisposableBean 类似于InitializingBean,对应生命周期的销毁阶段
ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了DisposableBean接口的Bean然后调用其destroy()方法
Spring IOC容器对bean的生命周期进行管理的过程
通过构造器或工厂方法创建bean实例(相当于servlet生命周期中的实例化)
1.Bean容器找到配置文件中Spring Bean的定义
2. Bean容器利用Java Reflection API创建一个Bean的实例
为bean的属性设置值和对其他bean的引用(依赖注入)
3. 如果涉及到一些属性值,利用set()方法设置一些属性值
Aware Group1
4.如果Bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法。
5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
6.如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
7.如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来
将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
添加bean后置处理器后bean的生命周期
8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。
9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法
调用bean的初始化方法
将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
添加bean后置处理器后bean的生命周期
11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization()方法。
bean可以使用了(实现功能)
当容器关闭时,调用bean的销毁方法
12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
哪些是重要的bean生命周期方法? 你能重载它们吗?
两个重要的bean 生命周期方法
setup
在容器加载bean的时候被调用
teardown
在容器卸载类的时候被调用
在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法
相应的注解(@PostConstruct和@PreDestroy)
bean的后置处理器(针对bean 进行的操作)
bean后置处理器允许在调用初始化方法前后对bean进行额外的处理
bean后置处理器(对Spring所管理的每一个bean都有效果)对IOC容器里的所有bean实例逐一处理,而非单一实例。
典型应用:检查bean属性的正确性或根据特定的标准更改bean的属性。
bean后置处理器需要实现接口
org.springframework.beans.factory.config.BeanPostProcessor
在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法
postProcessBeforeInitialization(Object, String)
postProcessAfterInitialization(Object, String)
一个 Spring Bean 定义包含什么?
一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。
如何给Spring 容器提供配置元数据?Spring有几种配置方式
XML配置文件
Spring配置文件包含了哪些信息
Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。
Spring基于xml注入bean的几种方式
Set方法注入
<property name="id" value="10010"/><br>
为属性赋值时,可以使用哪些值
字面量
能够写在双引号中来表示正常的含义
注意:引用数据类型不能使用这种方式,要使用ref
可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来
你可以在Spring中注入一个null 和一个空字符串吗?
可以
null值
<property name="score"><null/></property>
给bean的级联属性赋值
<property name="teacher" ref="teacher"/>
创建对象,对象来对teacher 进行赋值
使用Bean来管理一个对象
通过ref引用当前Spring所管理的范围之内的某一个Bean的ID
外部已声明的bean、引用其他的bean
引用外部属性文件
JDBC 连接池对象交个Spring来管理,如何来使用properties文件
什么是Spring的内部bean?Spring inner beans
在Spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype。
p名称空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。
Spring 从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean 的属性。
Spring 从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean 的属性。
<bean id="s3" class="org.xinzhang.di.Student" p:id="123" p:age="14" p:name="zx"/>
构造器注入
通过index设置参数的位置
<constructor-arg value="90" index="2" type="java.lang.Double"/>
通过type设置参数类型
静态工厂注入
实例工厂
基于注解的配置
基于java的配置
给bean的属性赋值(依赖注入的过程)
什么是 spring 装配?
Spring所管理的bean就是一个对象,而这个对象组成由多个属性和方法组成,方法是用来调用的,而属性是用来赋值的。
在为Spring所管理的对象进行赋值时,可叫做装载/注入。
在为Spring所管理的对象进行赋值时,可叫做装载/注入。
1)手动装配:以value或ref的方式明确指定属性值都是手动装配。
2)自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。(非字面量属性,需要ref的)
autowire:根据某种策略自动为非字面量属性赋值
byName:通过属性名和spring容器中bean的id进行比较,若一致则可直接赋值
无法赋值的例子(实体类中属性名 car,而spring中bean的id为car2)
无法赋值的例子(实体类中属性名 car,而spring中bean的id为car2)
根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同
byType:通过spring容器中bean的类型,为兼容性的属性赋值
兼容性:配置文件自动装配 子类可以为父类,接口赋值
在使用byType的过程中,要求spring容器中只能有一个能为属性赋值的bean
根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。
若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配
若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配
选用建议
当设置autowire属性,会作用于该bean中所有的非字面量属性,因此谁都不用
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现
通过注解配置bean
两步:添加注解,扫描组件
在需要被spring管理的类上加上相应注解
在配置文件中通过<context:component-scan>对所设置的包结构进行扫描,将加上注解的类,作为spring的组件进行加载
(组件理解为bean)
相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。
1000个对象管理,需要写1000个bean吗?一个类依赖于20个非字面量对象,只想为两个属性赋值,只依赖于两个对象。注解方式
使用注解标识组件(组件理解为bean)
Bean 对象由类产生,对象是类的实例化,确定对象属于哪个类,所有bean标签中class很重要,找到这个类,交个Spring来管理。使用注解方式直接加在类上
请求响应过程
控制层接收请求--service处理业务逻辑,dao处理持久化--结果返回给service--控制层--响应客户端
Spring组件: 组成部分 bean
普通组件:加上@Component
加在类上,就可以当作类的组件进行加载 ,Spring会对这个类进行管理
标识一个受Spring IOC容器管理的组件
表述层控制器组件:@Controller
SpringMVC控制层普普通通的类,不需要继承和实现,通过注解识别为控制层来加载。实现请求响应 springMVC封装了servlet
标识一个受Spring IOC容器管理的表述层控制器组件
业务逻辑层组件:@Service
service 业务逻辑层
servlet控制请求与响应
标识一个受Spring IOC容器管理的业务逻辑层组件
持久化层组件:@Repository
DAO(Data Access Object):数据访问层(持久层)
标识一个受Spring IOC容器管理的持久化层组件
四个注解一样,名字不一样,哪一层就加哪一层的注解
注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将 @Respository注解用在一个表述层控制器组件上面也不会产生任何错误,
所以 @Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确 当前的组件扮演的角色。
组件命名规则
默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id
使用组件注解的value属性指定bean的id
扫描组件
<context:component-scan>:扫描组件,对设置的包下面的类进行扫描,会将加上注解的类作为spring的组件进行加载
组件:指spring中管理的bean
作为spring的组件进行加载:会自动在spring的配置文件中生成相对应的bean,这些bean的id会以类的首字母小写为值
组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到
<context:component-scan base-package="org.xinzhang.ioc.annotationBean">
base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。
指定被扫描的package
当需要扫描多个包时可以使用逗号分隔。
如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类
包含与排除(在定位base-package的时候要尽量定位准确,加快包加载速度)
<context:include-filter>:在设定的包结构下,再次通过注解或类型具体包含到某个或某几个类
注意:在使用包含时,一定要设置use-default-filters="false",将默认的过滤(即扫描包下所有的类)关闭,仅包含某些组件
<context:exclude-filter>:在设定的包结构下,再次通过注解或类型排除某个或某几个类
注意:在使用排除时,一定要设置use-default-filters="true",将默认的过滤(即扫描包下所有的类)打开
切记:一个<context:component-scan>中可以出现多个include,也可以同时出现多个exclude,但是两个不能同时出现
过滤表达式type
annotation
根据目标组件是否标注了指定类型的注解进行过滤
<context:component-scan base-package=””>
<context:include-filter 中expression属性,找对应注解类中导入的类型
expression="import org.springframework.stereotype.Service"
<context:include-filter 中expression属性,找对应注解类中导入的类型
expression="import org.springframework.stereotype.Service"
assignable
根据目标组件是否是指定类型的子类的方式进行过滤
expression="org.xinzhang.ioc.annotationBean.dao.UserDaoMybatisImpl"
aspectj
regex
custom
组件装配
需求
Controller组件需要用到Service组件实例
Service组件需要用到 Repository组件实例
Spring通过注解实现属性装配
实现依据
在指定要扫描的包时,<context:component-scan> 元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor的实例
该后置处理器可以自动装配标记了@Autowired(自动装配,不需要手动去new 对象)、@Resource或@Inject注解的属性
Private UserService userService = new UserServiceImpl();
替换成:
@Autowaired
Private UserService userService;
替换成:
@Autowaired
Private UserService userService;
自动装配:在需要赋值的非字面量属性上,加@Autowired,可在spring容器中,通过不同方式匹配到相应bean
@Autowired注解
根据类型实现自动装配
构造器、普通字段(即使是非public)、一切具有参数的方法都可以应用@Autowired 注解
默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的bean装配属性时,会抛出异常
若某一属性允许不被设置,可以设置@Autowired注解的required属性为 false
默认情况下,当IOC容器里存在多个类型兼容的bean时,Spring会尝试匹配bean 的id值是否与变量名相同,如果相同则进行装配。如果bean的id值不相同,通过类 型的自动装配将无法工作。此时可以在@Qualifier注解里提供bean的名称。Spring 甚至允许在方法的形参上标注@Qualifiter注解以指定注入bean的名称。
@Autowired装配时,会默认使用byType的方式,此时要求spring容器中只有一个能够为其赋值
@Controller 生成首字母小写的bean userController
public class UserController
@Service 生成首字母小写的bean userServiceImpl
public class UserServiceImpl
@Repository 生成首字母小写的bean userDaoImpl
public class UserDaoImpl
public class UserController
@Service 生成首字母小写的bean userServiceImpl
public class UserServiceImpl
@Repository 生成首字母小写的bean userDaoImpl
public class UserDaoImpl
@Autowaired
Private UserService userService;
在自动装配时,没有与userService同名的id,所以是先以类型byType进行装配。
子类userServiceImpl 为接口 userService赋值
Private UserService userService;
在自动装配时,没有与userService同名的id,所以是先以类型byType进行装配。
子类userServiceImpl 为接口 userService赋值
当byType实现不了装配时,会自动切换到byName,此时要求spring容器中,有一个bean的id和属性名一致
若自动装配时,匹配到两个bean,id分别为:userDaoMybatisImpl和userDaoImpl,可使用@Qualifier(value=" userDaoMybatisImpl")
@Qualifier(value=" userDaoImpl") 指定使用的bean
@Repository
public class UserDaoMybatisImpl implements UserDao
@Repository
public class UserDaoImpl implements UserDao
有了自动装配@Autowired才能使用限定 @Qualifier(value="beanId")
装配多个,限定到某一个
public class UserDaoMybatisImpl implements UserDao
@Repository
public class UserDaoImpl implements UserDao
有了自动装配@Autowired才能使用限定 @Qualifier(value="beanId")
装配多个,限定到某一个
一个注解只对一个类一个属性一个方法有效
注解申明在方法上,是对参数有效果,对参数赋值之后才有值,才不是空指针异常
@Autowired
@Qualifier(value="userDaoMybatisImpl")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
注解申明在方法上,是对参数有效果,对参数赋值之后才有值,才不是空指针异常
@Autowired
@Qualifier(value="userDaoMybatisImpl")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的bean进行自动装配。
@Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean。
@Autowired注解用在java.util.Map上时,若该Map的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。
@Resource
@Resource注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为bean的名称。
@Inject
@Inject和@Autowired注解一样也是按类型注入匹配的bean,但没有reqired属性
AOP代理模式
动态代理(三个角色:原始对象,代理对象,目标对象)
代理对象:帮助我们完成这些事情的对象
目标对象:我们要做的事情
动态代理:不管我的目标对象是什么,都可以通过这样的一个类去生成相对应的代理对象帮助我去完成功能。
更像一个工具类
计算器的目的是加减乘除方法,方法存在于类中所以类名是目标对象
Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {@Override……}
ClassLoader loader = this.getClass().getClassLoader();
类由类加载器加载,当前代理对象所属类的类加载器,来创建代理对象。
Class[] interfaces = mathImpl(目标对象).getClass().getInterfaces();
代理对象帮助我们完成功能,要知道我们的功能实现哪些接口,实现哪些功能。
InvocationHandler:执行处理器:设置代理对象如何实现目标对象功能 invoke方法,匿名内部类的方法创建对象
Public Object invoke(Object proxy ,Method method ,Object[] args) {
method.invoke(指定调用方法的对象matgImpl,执行方法时的形参列表args );
}
method.invoke(指定调用方法的对象matgImpl,执行方法时的形参列表args );
}
参数一:代理对象 proxy
参数二:帮助实现功能(方法)
代理模式:保证结果的一致性,代理对象调用目标对象的方法
方法的执行依赖于对象,有对象才能调用方法,知道方法名,知道参数列表才能确定调用哪个方法。
ProxyUtil proxy = new ProxyUtil(new MathImpl()); //构造器方式直接赋值
MathI math = (MathI)proxy.getProxy();
//绝不能强转为目标对象, 转换为目标对象所实现的 接口 涵括更多功能
Object向下转型
MathI math = (MathI)proxy.getProxy();
//绝不能强转为目标对象, 转换为目标对象所实现的 接口 涵括更多功能
Object向下转型
AOP
(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传
统 OOP(Object-Oriented Programming,面向对象编程)的补充
统 OOP(Object-Oriented Programming,面向对象编程)的补充
面向对象
纵向继承机制
功能实现过程调用代码
面向切面
横向抽取机制
依赖于动态代理
功能抽取出来作用于代码(非业务逻辑(日志功能)—>业务逻辑(增删改查))
把目标对象中的 公共部分(功能)--横切关注点,非业务功能抽取出来所存放的类就叫做切面
AOP图解
每个事物逻辑位于一个位置,代码不分散,便于维护和升级
业务模块更简洁,只包含核心业务代码
AOP术语
横切关注点
从每个方法中抽取出来的同一类非核心业务。
验证参数
前置日志
后置日志
切面(Aspect)
封装横切关注点信息的类,每个关注点体现为一个通知方法。
AspectJ
AspectJ大量注解(具体实现):Java社区里最完整最流行的AOP(思想)。对比原生:xml配置框架。
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
第一步:配置文件xml中扫描组件<context:component-scan base-package="org.xinzhang.aop"/>
第二步:配置<aop:aspectj-autoproxy />
当Spring IOC容器侦测到bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为 与AspectJ切面匹配的bean创建代理
第三步:在切面上定义两个注解 组件注解 @Component+ 切面注解@Aspect(标注当前类为切面)
@Order(1)
定义切面作用的优先级,值越小优先级越高,默认值为int的最大值
Servlet在第一次访问时进行加载,让servlet提前到项目启动时加载,使用标签loadstartup
数字代表加载优先级,值小优先级越高
数字代表加载优先级,值小优先级越高
通知(Advice)
把横切关注点加入到切面里叫通知
切面必须要完成的各个具体工作
在切面上写通知
@Before:前置通知,在方法执行之前执行
@Before(value = "execution(public int com.atguigu.spring.aop.MathImpl.add(int, int))")
@Before(value="execution(* com.atguigu.spring.aop.*.*(..))")
第一个*代表任意的访问修饰符和返回值类型
第二个*代表任意类
第三个*代表类中任意方法
.. 两个点代表任意的参数列表
必须设置value,其值为切入点表达式
@After:后置通知,作用于方法的finally语句块,即不管有没有异常都会执行
@AfterReturning(value = "myTestReuse()" ,returning = "result")
public void myTestAfterRunning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("return ... method " + methodName + ",result:"+ result);
}
public void myTestAfterRunning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("return ... method " + methodName + ",result:"+ result);
}
@AfterRunning:返回通知,在方法返回结果之后执行
可通过returning设置接收方法返回值的变量名
要想在方法中使用,必须在方法的形参中设置和变量名相同的参数名的参数
如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。
@AfterThrowing(value = "myTestReuse()" , throwing = "ex")
public void myTestAfterThrowing(ArithmeticException ex) {
System.out.println("throwing ... message:" + ex);
}
public void myTestAfterThrowing(ArithmeticException ex) {
System.out.println("throwing ... message:" + ex);
}
@AfterThrowing:异常通知,在方法抛出异常之后执行
可通过throwing设置接收方法返回的异常信息
在参数列表中可通过具体的异常类型,来对指定的异常信息进行操作
@Around:环绕通知,围绕着方法执行
切入点(pointcut)
条件:切面要作用到谁
AOP可以通过切入点定位到特定的连接点。
切入点表达式:作用于切入点
通知作用于哪一个包下哪一个类名的哪一个方法
通过解析表达式找到切面所应用到的连接点
@Pointcut(value = "execution(* com.atguigu.spring.aop.*.*(..))")
public void myTestReuse() {}
public void myTestReuse() {}
1)在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现。
2)在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的。
3)切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。
4)其他通知可以通过方法名称引入该切入点
连接点(Joinpoint)
@Before(value = "execution(public int com.atguigu.spring.aop.MathImpl.add(int, int))")
public void beforeMethod(JoinPoint joinPoint) {
// System.out.println("add 方法前置条件");
Object[] args = joinPoint.getArgs();//获取方法的参数
String methodName = joinPoint.getSignature().getName();//获取方法名
public void beforeMethod(JoinPoint joinPoint) {
// System.out.println("add 方法前置条件");
Object[] args = joinPoint.getArgs();//获取方法的参数
String methodName = joinPoint.getSignature().getName();//获取方法名
切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。
那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息
例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中。
目标(Target)
切面要作用于谁
被通知的对象(前置通知:方法执行之前,后置通知,返回通知,异常通知,环绕通知(包含四个))
代理(Proxy)
AOP 加入前置后置等操作
Spring事务管理(AOP最经典的体现)
事务概述
一个业务逻辑需要多次操作才能完成业务逻辑,在这个业务逻辑中所有的数据库操作就在一个事务中
买书过程:查询图书库存以及价格 ,在库存中减一,客户账户扣费,对数据库的多次操作就是一个事务
使用事务保证数据的完整性和一致性
事务(多次操作):要么都执行,要么都不执行
事务:加在service中的方法代表一个完整的业务逻辑,完整的功能 完成事务四特性
DAO中每一个方法都是对数据库的单独一次操作
事务管理的代码属于非核心业务
事务的四大属性ACID
原子性(atomicity)
"原子" 本意“不可再分”,事务的原子性表现为一个事务中涉及的多个操作在逻辑上缺一不可。要求事务中的所有操作要么都执行,要么都不执行。
一致性(consistency)
“一致”指的是数据的一致
所有数据都处于满足业务规则的一致性状态。如:转账前后金额一致
一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的
回滚:如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态
隔离性(isolation)
在应用程序实际运行过程中,事务往往是并发执行的,所以有可能许多事务同时处理相同数据,因此每个事务都应与其他事务隔离开来,防止数据损坏
隔离性原则要求多个事务在并发执行过程中不会互相干扰
持久性(durability)
持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。
通常情况下,事务对数据的修改应该被写入到持久化存储器(硬盘)中
Spring事务的实现方式和实现原理
Spring事务本质:数据库对事务的支持,没有数据库的事务支持,spring无法提供事务功能。真正的数据库层的事务提交和回滚通过binlog或者redo log实现
Spring支持的事务管理类型, spring 事务实现方式有哪些?
编程式事务管理
通过编程的方式管理事务,极大的灵活性,但难维护
声明式事务管理
将业务代码和事务管理分离,只需用注解和XML配置来管理事务
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
Spring在不同的事务管理API之上定义一个抽象层,通过配置使其生效,不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。
说一下Spring的事务传播行为
当多个事务同时存在时,spring如何处理这些事务的行为
支持当前事务的情况
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
本来期望目标方法进行事务管理,但若是错误的配置这三种 可以不启动事务的propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。mandatory命令性强制性
不支持当前事务的情况
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 如果当前存在事务,则把当前事务挂起,创建一个新的事务,
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 如果当前存在事务,则把当前事务挂起,如果当前没有事务,则以非事务方式运行,
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常
其他情况
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
说一下 spring 的事务隔离?
TransactionDefinition 接口中定义了五个表示隔离级别的常量
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.*)
isolation:事务的隔离级别,在并发的情况下,操作数据的一种规定,多条请求处理
TransactionDefinition.ISOLATION_DEFAULT
使用后端数据库默认的隔离级别
Mysql 默认采用的 REPEATABLE_READ隔离级别
Oracle 默认采用的 READ_COMMITTED隔离级别
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
读未提交:最低的隔离级别,允许读取尚未提交的数据变更
可能会导致脏读、幻读或不可重复读
读未提交:脏读
假设现在有两个事务:Transaction01和Transaction02并发执行
①Transaction01将某条记录的age值从20修改为30。
②Transaction02读取了Transaction01更新后的值:30。
③Transaction01回滚,AGE值恢复到了20。
④Transaction02读取到的30就是一个无效的值。
①Transaction01将某条记录的age值从20修改为30。
②Transaction02读取了Transaction01更新后的值:30。
③Transaction01回滚,AGE值恢复到了20。
④Transaction02读取到的30就是一个无效的值。
TransactionDefinition.ISOLATION_READ_COMMITTED
读已提交:允许读取并发事务已经提交的数据
可以阻止脏读,但是幻读或不可重复读仍有可能发生
读已提交:不可重复读
①Transaction01读取了AGE值为20。
②Transaction02将age值修改为30。
③Transaction01再次读取AGE值为30,和第一次读取不一致。
②Transaction02将age值修改为30。
③Transaction01再次读取AGE值为30,和第一次读取不一致。
TransactionDefinition.ISOLATION_REPEATABLE_READ
可重复读:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
可以阻止脏读和不可重复读,但幻读仍有可能发生
可重复读:幻读 , 正在读取的数据,其他人不能操作,可以添加新数据
①Transaction01读取了student表中的一部分数据(10条)。
②Transaction02向student表中插入了新的行(10条)。
③Transaction01读取了student表时,多出了一些行(再查成20条,幻读)。
②Transaction02向student表中插入了新的行(10条)。
③Transaction01读取了student表时,多出了一些行(再查成20条,幻读)。
TransactionDefinition.ISOLATION_SERIALIZABLE
序列化(单线程):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰
该级别可以防止脏读、不可重复读以及幻读
严重影响程序的性能
触发事务回滚的异常
rollbackFor(设置因为什么进行回滚,不因为什么回滚):异常所对应的权限对象
rollbackFor | rollbackForClassName | noRollbackFor | noRollbackForClassName:设置事务回滚的条件
noRollbackFor 不会因为抛出MyException异常而回滚 该成功该失败失败
默认情况
捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚
Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,也可以保证数据的一致性。
当@Transactional注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。
设置途经
注解@Transactional
rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个
@Transactional(propagation=Propagation.REQUIRES_NEW, timeout=3, rollbackFor={IOException.class,SQLException.class},noRollbackFor= {NullPointerException.class, MyException.class})
noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个
事务的超时和只读属性
timeout
在事务 强制回滚 前最多可以执行(等待)的时间秒杀活动,一万个请求在等待,给请求设置timeout 压力测试:超时中断线程调度设置超时时间
readOnly
指定当前事务中的一系列的操作是否为只读 读本身不涉及事务
若设置为只读,不管事务中有没有写的操作,mysql都会在请求访问数据的时候,不加锁,提高性能
脏读:读未提交,不可重复读:读已提交,可重复读:幻读。
mysql给加锁,其他请求不可操作,面向增删改操作,不加锁可能出现一千个请求去查看修改一条数据.但是如果有写操作的情况,建议一定不能设置只读
软件性能
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
爱的鼓励
0 条评论
下一页