Spring思维导图(知识点总结,含面试题整理)
2021-10-26 17:36:49 35 举报
AI智能生成
Spring思维导图是一个全面的知识总结工具,它详细地列出了Spring框架的所有主要组件和功能。这个思维导图不仅包括Spring的核心概念,如依赖注入、控制反转、面向切面编程等,还包括各种高级特性,如事务管理、安全、消息传递等。此外,Spring思维导图还包含了大量与Spring相关的面试题,帮助学习者检验自己的理解程度并提高面试技巧。无论你是初学者还是有经验的开发者,Spring思维导图都能为你提供一个清晰、结构化的学习路径,帮助你更有效地掌握Spring框架。
作者其他创作
大纲/内容
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框架中有哪些不同类型的事件
上下文开始事件(ContextStartedEvent)
当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件
请求处理事件(RequestHandledEvent)
在Web应用中,当一个http请求(request)结束触发该事件。
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知
上下文更新事件(ContextRefreshedEvent)
在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发
上下文关闭事件(ContextClosedEvent)
当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁
上下文停止事件(ContextStoppedEvent)
当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件
Spring框架中有哪些不同类型的事件
Spring的ApplicationContext 提供了支持事件和代码中监听器的功能。我们可以创建bean用来监听在ApplicationContext 中发布的事件。
ApplicationEvent类和在ApplicationContext接口中处理的事件,如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
public class AllApplicationEventListener implements ApplicationListener <ApplicationEvent>{
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//process event
}
}
Spring 提供了以下5中标准的事件:
上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
ApplicationEvent类和在ApplicationContext接口中处理的事件,如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
public class AllApplicationEventListener implements ApplicationListener <ApplicationEvent>{
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//process event
}
}
Spring 提供了以下5中标准的事件:
上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
自定义事件
扩展ApplicationEvent 类来开发自定义的事件。
public class CustomApplicationEvent extends ApplicationEvent{
public CustomApplicationEvent ( Object source, final String msg ){
super(source);
System.out.println("Created a Custom event");
}
}
为了监听这个事件,还需要创建一个监听器:
public class CustomEventListener implements ApplicationListener <CustomApplicationEvent>{
@Override
public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
//handle event
}
}
之后通过applicationContext接口的publishEvent()方法来发布自定义事件。
CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);
public class CustomApplicationEvent extends ApplicationEvent{
public CustomApplicationEvent ( Object source, final String msg ){
super(source);
System.out.println("Created a Custom event");
}
}
为了监听这个事件,还需要创建一个监听器:
public class CustomEventListener implements ApplicationListener <CustomApplicationEvent>{
@Override
public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
//handle event
}
}
之后通过applicationContext接口的publishEvent()方法来发布自定义事件。
CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);
AOP(Aspect Oriented Programming)和设备支持(Instrmentation)
AOP也是依赖于IOC
spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等
消息(Messaging)
Web
spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext
Test
spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
Spring 框架中都用到了哪些设计模式?
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
单例模式:Bean默认为单例模式
模板方法:用来解决代码重复的问题。比如. 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 管理对象 配置到配置文件中
控制反转(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根目录的路径中完成初始化工作
数据访问与集成(Data Access/Integeration)
从下至上。ORM对象关系映射 用在持久层框架中,从数据库中查询出来的数据自动赋值给对象。操作数据库像操作实体类一样简单
spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC
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的子接口。
其中ApplicationContext是BeanFactory的子接口。
依赖关系
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,
管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,
还提供了更完整的框架功能
还提供了更完整的框架功能
继承MessageSource,因此支持国际化
统一的资源文件访问方式
提供在监听器中注册bean的事件
同时加载多个配置文件
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
加载方式
BeanFactroy采用的是延迟加载(懒加载)形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),
才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性
没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性
没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以
发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入
所有的单实例Bean,通过预载入单实例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)这两个功能。我们可以称之为 “低级容器”
通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了
多个接口。因此具备了更多的功能。例如资源的获取ResourcePatternResolve,支持多种消息
(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经
不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口
定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,
即重新加载/刷新所有的 bean。
多个接口。因此具备了更多的功能。例如资源的获取ResourcePatternResolve,支持多种消息
(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经
不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口
定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,
即重新加载/刷新所有的 bean。
为了更直观的展示 “低级容器” 和 “高级容器” 的关系,这里通过常用的 ClassPathXmlApplicationContext
类来展示整个容器的层级 UML 关系
类来展示整个容器的层级 UML 关系
最上面的是 BeanFactory,下面的 3 个绿色的,都是功能扩展接口
看下面的隶属 ApplicationContext 粉红色的 “高级容器”,依赖着 “低级容器”,这里说的是依赖,
不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,
可以访问文件资源,支持应用事件(Observer 模式)
不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,
可以访问文件资源,支持应用事件(Observer 模式)
通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦
左边灰色区域的是 “低级容器”, 只负载加载 Bean,获取 Bean。容器其他的高级功能是没有的。
例如上图画的 refresh 刷新 Bean 工厂所有配置,生命周期事件回调等
例如上图画的 refresh 刷新 Bean 工厂所有配置,生命周期事件回调等
小结
IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤
加载配置文件,解析成 BeanDefinition 放在 Map 里
调用 getBean 时,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,
同时,如果有依赖关系,将递归调用 getBean 方法 ,完成依赖注入
同时,如果有依赖关系,将递归调用 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 定义包含什么?
一个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文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现
装配的类型
no:顾名思义, 显式指明不使用Spring的自动装配功能
byName:根据属性和组件的名称匹配关系来实现bean的自动装配
byType:根据属性和组件的类型匹配关系来实现bean的自动装配,有多个适合类型的对象时装配失败
constructor:与byType类似是根据类型进行自动装配,但是要求待装配的bean有相应的构造函数
在constructor模式下,存在单个实例则优先按类型进行参数匹配(无论名称是否匹配),当存在多个类型相同实例时,按名称优先匹配,如果没有找到对应名称,则注入失败,此时可以使用autowire-candidate=”false” 过滤来解决。
autodetect:autodetect是constructor与byType的组合,会先进行constructor,如果不成功,再进行byType。
通过注解配置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属性
@value
作用:对简单类型的属性自动注入
@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值
@Value会与properties文件结合使用
SpEL
//SpEL表达方式,其中代表xml配置文件中的id值configProperties
@Value( #{configProperties[ jdbc.username ]} )
private String userName;
@Value( #{configProperties[ jdbc.username ]} )
private String userName;
配置
占位符
//占位符方式
@Value( ${jdbc.url} )
private String url;
@Value( ${jdbc.url} )
private String url;
配置
手工装配依赖对象
* 在xml配置文件中,通过在bean节点下配置
属性注入
通过setXxx()方法注入Bean的属性值或依赖对象
注入简单值
<property name= name value= 陈冠希 ></property>
注入其他bean
<property name= address ref= address ></property>
注入数组
配置
注入集合
注入list
配置
注入set
配置
注入map
配置
注入空值
<propertyname= wife ><null/></property>
注入Properties
配置
属性过多时,构造函数变的臃肿可怕构造函数注入灵活性不强,有时需要为属性注入null值多个构造函数时,配置上产生歧义,复杂度升高构造函数不利于类的继承和扩展构造函数注入会引起循环依赖的问题
构造函数注入
使用构造函数注入的前提是Bean必须提供带参数的构造函数
<constructor-arg ref= logDao ></constructor-arg>
构造函数保证重要属性预先设置无需提供每个属性Setter方法,减少类的方法个数可更好的封装类变量,避免外部错误调用
静态工厂
代码
<bean id= car6 class= com.baobaotao.ditype.CarFactory factory-method= createCar ></bean>
实例工厂
代码
<!-- 工厂方法-->
<bean id= carFactory class= com.baobaotao.ditype.CarFactory />
<bean id= car5 factory-bean= carFactory factory-method= createHongQiCar > </bean>
<bean id= carFactory class= com.baobaotao.ditype.CarFactory />
<bean id= car5 factory-bean= carFactory factory-method= createHongQiCar > </bean>
声明Bean
基于java类提供Bean定义(配置类)(需要提供setter方法)
@Configuation
@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,作用为:配置spring容器(应用上下文)
@Configuration启动容器+@Bean注册Bean,@Bean下管理bean的生命周期
代码
(1)、@Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同;
(2)、@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域;
(3)、既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
(2)、@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域;
(3)、既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
@Configuration启动容器+@Component注册Bean
配置类的注册方式
代码
代码
配置Web应用程序(web.xml中配置AnnotationConfigApplicationContext)
之前
配置
之后
配置
您要将 web.xml 中的上述代码更改为使用 AnnotationConfigApplicationContext 类。切记,XmlWebApplicationContext 是 Spring 为 Web 应用程序使用的默认上下文实现,因此您永远不必在您的web.xml 文件中显式指定这个上下文类。现在,您将使用基于 Java 的配置,因此在配置 Web 应用程序时,需要在web.xml 文件中指定 AnnotationConfigApplicationContext 类。现在定义了 AnnotationConfigWebApplicationContext 上下文类,并将其作为上下文参数和 servlet 元素的一部分。上下文配置位置现在指向 AppContext 配置类。
@Configuation总结
@Configuation等价于<Beans></Beans>
@Bean等价于<Bean></Bean>
@ComponentScan等价于<context:component-scan base-package="com.dxz.demo" />
@Bean等价于<Bean></Bean>
@ComponentScan等价于<context:component-scan base-package="com.dxz.demo" />
组合多个配置类
在@configuration中引入spring的xml配置文件
@Configuration
@ImportResource("classpath:applicationContext-configuration.xml")
public class WebConfig {}
@ImportResource("classpath:applicationContext-configuration.xml")
public class WebConfig {}
在@configuration中引入其它注解配置
@Configuration
@ImportResource("classpath:applicationContext-configuration.xml")
@Import(TestConfiguration.class)
public class WebConfig {}
@ImportResource("classpath:applicationContext-configuration.xml")
@Import(TestConfiguration.class)
public class WebConfig {}
@configuration嵌套(嵌套的Configuration必须是静态类)
代码
http://www.cnblogs.com/duanxz/p/7493276.html
https://blog.csdn.net/isea533/article/details/78072133?locationNum=7&fps=1
代码
bean的延迟加载
在某些情况下,我们可能希望把bean的创建延迟到使用阶段,以免消耗不必要的内存,Spring也非常自愿地支持了延迟bean的初始化。因此可以在配置文件中定义bean的延迟加载,这样Spring容器将会延迟bean的创建直到真正需要时才创建。通常情况下,从一个已创建的bean引用另外一个bean,或者显示查找一个bean时会触发bean的创建即使配置了延迟属性,因此如果Spring容器在启动时创建了那些设为延长加载的bean实例,不必惊讶,可能那些延迟初始化的bean可能被注入到一个非延迟创建且作用域为singleton的bean。在xml文件中使用bean的lazy-init属性可以配置改bean是否延迟加载,如果需要配置整个xml文件的bean都延迟加载则使用defualt-lazy-init属性,请注意lazy-init属性会覆盖defualt-lazy-init属性。
default-lazy-init="true"
<!-- lazy-init="false" 表示非延长加载-->
<bean name="accountDao" lazy-init="false" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
<bean name="accountDao" lazy-init="false" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
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 加入前置后置等操作
实现原理
jdk动态代理
主要通过Proxy.newProxyInstance()和InvocationHandler这两个类和方法实现
实现过程
创建代理类proxy实现Invocation接口,重写invoke()方法
调用被代理类方法时默认调用此方法
将被代理类作为构造函数的参数传入代理类proxy
调用Proxy.newProxyInsatnce(classloader,interfaces,handler)方法生成代理类
生成的代理类
$Proxy0 extends Proxy implements Person
类型为$Proxy0
因为已经继承了Proxy,所以java动态代理只能对接口进行代理
代理对象会实现用户提供的这组接口,因此可以将这个代理对象强制类型转化为这组接口中的任意一个
通过反射生成对象
总结: 代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。
cglib
生成对象类型为Enhancer
实现原理类似于 jdk 动态代理,只是他在运行期间生成的代理对象是针
对目标类扩展的子类
静态代理
缺点
如果要代理一个接口的多个实现的话需要定义不同的代理类
代理类 和 被代理类 必须实现同样的接口,万一接口有变动,代理、被代理类都得修改
在编译的时候就直接生成代理类
JDK动态代理和cglib的对比
CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高
1.6和1.7的时候,CGLib更快
1.8的时候,jdk更快
CGLib在创建对象的时候所花费的时间却比JDK动态代理多
singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反之,则适合用JDK动态代理
JDK动态代理是面向接口的,CGLib动态代理是通过字节码底层继承代理类来实现(如果被代理类被final关键字所修饰,那么会失败)
JDK生成的代理类类型是Proxy(因为继承的是Proxy),CGLIB生成的代理类类型是Enhancer类型
如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
如果要被代理的对象不是实现类,那么Spring会强制使用CGLib来实现动态代理。
AOP术语
AOP概念
在AOP的编程方法中,主要在于对关注点的提起及抽象。我们可以把一个复杂的系统看作是由多个关注点来有机组合来实现,一个典型的系统可能会包括几个方面的关注点,如核心业务逻辑、性能、数据存储、日志、授权、安全、线程及错误检查等,另外还有开发过程中的关注点,如易维护、易扩展等。把每个关注点与核心业务模块分离,作为单独的功能,横切几个核心业务模块,使得每份功能代码不再单独入侵到核心业务类的代码中,即核心模块只需关注自己相关的业务,当需要外围业务(日志,权限,性能监测、事务控制)时,这些外围业务会通过一种特殊的技术自动应用到核心模块中,这些关注点有个特殊的名称,叫做“横切关注点”,
关注点
关注点就是我们要考察或解决的问题。如订单的处理,用户的验证、用户日志记录等都属于关注点。
核心关注点
是指系统中的核心功能,即真正的商业逻辑。如在一个电子商务系统中,订单处理、客户管理、库存及物流管理都是属于系统中的核心关注点。
横切关注点
他们分散在每个各个模块中解决同一样的问题,跨越多个模块。如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
Aspect 切面
切面是一个关注点的模块化,这个关注点可能会横切多个对象和模块,事务管理是横切关注点的很好的例子。切面是通知和切点的结合。通知和切点定义了切面的全部内容——它是什么,在何时何处完成其功能。
切面优先级
目标方法前执行(”进入”)的通知函数,最高优先级的通知将会先执行,在执行在目标方法后执行(“退出”)的通知函数,最高优先级会最后执行。而对于在同一个切面定义的通知函数将会根据在类中的声明顺序执行。
实现org.springframework.core.Ordered 接口,该接口用于控制切面类的优先级,同时重写getOrder方法,定制返回值,返回值(int 类型)越小优先级越大。
通知(增强)Advice
增强是织入到目标类连接点上的一段程序代码,通知定义了切面要完成的工作以及何时使用(该什么时候执行),应该应用在某个方法被调用之前?之后?还是抛出异常时?等等。
5种通知类型
Before 前置通知
在方法调用之前调用通知
After 后置通知
在方法完成之后调用通知,无论方法执行成功与否
After-returning 后置返回通知
在方法执行成功之后调用通知
可以通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同
After-throwing 异常通知
在方法抛出异常后进行通知
由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可,
Around 环绕通知
通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,proceed()的返回值就是环绕通知的返回值。同样的,ProceedingJoinPoint对象也是可以获取目标对象的信息,如类名称,方法参数,方法名称等等。
5种通知上都可以使用JoinPoint参数,是Spring提供的静态变量,通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等,,该参数是可选的。
JoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; Signature getSignature() : 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息; getSignature().getName();获取调用的方法名 java.lang.Object getTarget() :获取连接点所在的目标对象; java.lang.Object getThis() :获取代理对象本身;
ProceedingJoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; Signature getSignature() : 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息; getSignature().getName();获取调用的方法名 java.lang.Object getTarget() :获取连接点所在的目标对象; java.lang.Object getThis() :获取代理对象本身; java.lang.Object proceed() throws java.lang.Throwable://执行目标方法 java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable://传入的新的参数去执行目标方法
Join point 连接点
每个方法都是一个连接点就是spring允许你执行通知(Advice)的地方,那可就真多了,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。
切点Pointcut
(具体在哪些方法上织入通知、这是条件,筛选出需要织入的连接点)切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附近都织入通知(使用叫织入),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
切入点指示符
通配符
..
匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
+
匹配给定类的任意子类
*
匹配任意数量的字符
类型签名表达式(within)
只能指定(限定)包路径(类名也可以看做是包路径),表示某个包下或者子包下的所有方法
语法格式
within(<type name>)
案例
//匹配com.zejian.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.zejian.dao..*)")
//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl)")
//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl+)")
//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.zejian.dao.UserDao+)")
@Pointcut("within(com.zejian.dao..*)")
//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl)")
//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl+)")
//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.zejian.dao.UserDao+)")
方法签名表达式(execution)
根据方法签名进行过滤
语法格式
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
案例
//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")
@Pointcut("execution(* com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")
bean
Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
案例
//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")private void myPointcut1(){}
@Pointcut("bean(*Service)")private void myPointcut1(){}
this
用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
案例
//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
target
用于匹配当前目标对象类型的执行方法;
案例
//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
@target():限制连接点匹配目特定的执行对象,这些对象对应的类要具备指定类型的注解.
@within
用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;
案例
//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
@Pointcut("@within(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
@annotation(com.zejian.spring.MarkerMethodAnnotation)
根据所应用的注解进行方法过滤
案例
//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
args()
限制连接点匹配参数为指定类型的执行方法,限定方法的参数的类型
案例
args(net.aazj.pojo.User): 参数为User类型的方法。
向注解处理方法传递参数
@Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)")
public void before3(int userId) {
System.out.println("userId-----" + userId);
}
它会拦截 net.aazj.service 包下或者子包下的getUser方法,并且该方法的第一个参数必须是int型的, 那么使用切点表达式args(userId,..) 就可以使我们在切面中的处理方法before3中可以访问这个参数。
public void before3(int userId) {
System.out.println("userId-----" + userId);
}
它会拦截 net.aazj.service 包下或者子包下的getUser方法,并且该方法的第一个参数必须是int型的, 那么使用切点表达式args(userId,..) 就可以使我们在切面中的处理方法before3中可以访问这个参数。
@AfterReturning( pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)", returning="returnValue")
public void access(Date time, Object returnValue, String name) {
System.out.println("目标方法中的参数String = " + name);
System.out.println("目标方法中的参数Date = " + time);
System.out.println("目标方法的返回结果returnValue = " + returnValue);
}
表达式中增加了args(time, name)部分,意味着可以在增强处理方法(access方法)中定义time和name两个属性——这两个形参的类型可以随意指定,但一旦指定了这两个参数的类型,则这两个形参类型将用于限制该切入点只匹配第一个参数类型为Date,第二个参数类型为name的方法(方法参数个数和类型若有不同均不匹配)。
public void access(Date time, Object returnValue, String name) {
System.out.println("目标方法中的参数String = " + name);
System.out.println("目标方法中的参数Date = " + time);
System.out.println("目标方法的返回结果returnValue = " + returnValue);
}
表达式中增加了args(time, name)部分,意味着可以在增强处理方法(access方法)中定义time和name两个属性——这两个形参的类型可以随意指定,但一旦指定了这两个参数的类型,则这两个形参类型将用于限制该切入点只匹配第一个参数类型为Date,第二个参数类型为name的方法(方法参数个数和类型若有不同均不匹配)。
@args()
限制连接点匹配参数由指定注解标注的执行方法
案例
@args(org.springframework.transaction.annotation.Transactional): 参数的类型 带有 @Transactional 注解 的所有方法
bean()
指定某个bean的名称
案例
bean(userService): bean的id为 "userService" 的所有方法;bean(*Service): bean的id为 "Service"字符串结尾的所有方法;
切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或者&&、||、!)
引入Introduction
引入是指给一个现有类添加方法或字段属性,引入还可以在不改变现有类代码的情况下,让现有的Java类实现新的接口(以及一个对应的实现)。相对于Advice可以动态改变程序的功能或流程来说,引介(Introduction)则用来改变一个类的静态结构。比如你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制
织入Weaving
“引入”我把它看做是一个定义,也就是一个名词,而“织入”我把它看做是一个动作,一个动词,也就是切面在指定的连接点被织入到目标对象中。简单理解为aspect(切面)应用到目标函数(类)的过程
织入方式
运行时织入
动态织入(Spring AOP)
在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的
动态代理技术
Java JDK的动态代理(Proxy,底层通过反射实现) 必须要有接口
示例
//自定义的接口类,JDK动态代理的实现必须有对应的接口类
public interface ExInterface { void execute();}
//A类,实现了ExInterface接口类
public class A implements ExInterface{
public void execute(){
System.out.println("执行A的execute方法...");
}
}
//代理类的实现
public class JDKProxy implements InvocationHandler{
/**
* 要被代理的目标对象 */
private A target;
public JDKProxy(A target){
this.target=target;
}
/**
* 创建代理类
* @return
* @param loader 类加载器,一般传递目标对象(A类即被代理的对象)的类加载器
* @param interfaces 目标对象(A)的实现接口
* @param InvocationHandler接口 回调处理句柄(后面会分析到)
*/
public ExInterface createProxy(){
return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
/** * 调用被代理类(目标对象)的任意方法都会触发invoke方法
* @param proxy 代理类 * @param method 被代理类的方法
* @param args 被代理类的方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//过滤不需要该业务的方法
if("execute".equals(method.getName())) {
//调用前验证权限
AuthCheck.authCheck();
//调用目标对象的方法
Object result = method.invoke(target, args);
//记录日志数据
Report.recordLog();
return result;
}eles if("delete".equals(method.getName())){
//.....
}
//如果不需要增强直接执行原方法
return method.invoke(target,args);
}
}
//测试验证
public static void main(String args[]){
A a=new A();
//创建JDK代理
JDKProxy jdkProxy=new JDKProxy(a);
//创建代理对象
ExInterface proxy=jdkProxy.createProxy();
//执行代理对象方法
proxy.execute();
}
public interface ExInterface { void execute();}
//A类,实现了ExInterface接口类
public class A implements ExInterface{
public void execute(){
System.out.println("执行A的execute方法...");
}
}
//代理类的实现
public class JDKProxy implements InvocationHandler{
/**
* 要被代理的目标对象 */
private A target;
public JDKProxy(A target){
this.target=target;
}
/**
* 创建代理类
* @return
* @param loader 类加载器,一般传递目标对象(A类即被代理的对象)的类加载器
* @param interfaces 目标对象(A)的实现接口
* @param InvocationHandler接口 回调处理句柄(后面会分析到)
*/
public ExInterface createProxy(){
return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
/** * 调用被代理类(目标对象)的任意方法都会触发invoke方法
* @param proxy 代理类 * @param method 被代理类的方法
* @param args 被代理类的方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//过滤不需要该业务的方法
if("execute".equals(method.getName())) {
//调用前验证权限
AuthCheck.authCheck();
//调用目标对象的方法
Object result = method.invoke(target, args);
//记录日志数据
Report.recordLog();
return result;
}eles if("delete".equals(method.getName())){
//.....
}
//如果不需要增强直接执行原方法
return method.invoke(target,args);
}
}
//测试验证
public static void main(String args[]){
A a=new A();
//创建JDK代理
JDKProxy jdkProxy=new JDKProxy(a);
//创建代理对象
ExInterface proxy=jdkProxy.createProxy();
//执行代理对象方法
proxy.execute();
}
Proxy.newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h)完全可以把InvocationHandler看成一个回调函数(Callback),Proxy方法创建代理对象proxy后,当调用任何方法(接口中的所有方法)时,将会回调InvocationHandler#invoke方法,因此我们可以在invoke方法中来控制被代理对象(目标对象)的方法执行,从而在该方法前后动态增加其他需要执行的业务。
CGLIB的动态代理(底层通过继承实现) 字节码生成技术实现AOP
示例
//被代理的类即目标对象
public class A {
public void execute(){
"执行A的execute方法...");
}
}
//代理类
public class CGLibProxy implements MethodInterceptor {
/**
* 被代理的目标类
*/
private A target;
public CGLibProxy(A target) {
super();
this.target = target;
}
/**
* 创建代理对象
* @return
*/
public A createProxy(){
// 使用CGLIB生成代理:
// 1.声明增强类实例,用于生产代理类
Enhancer enhancer = new Enhancer();
// 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
enhancer.setSuperclass(target.getClass());
// 3.//设置回调函数,即一个方法拦截
enhancer.setCallback(this);
// 4.创建代理:
return (A) enhancer.create();
}
/**
* 回调函数
* @param proxy 代理对象
* @param method 委托类方法
* @param args 方法参数
* @param methodProxy 每个被代理的方法都对应一个MethodProxy对象,
* methodProxy.invokeSuper方法最终调用委托类(目标类)的原始方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//过滤不需要该业务的方法
if("execute".equals(method.getName())) {
//调用前验证权限(动态添加其他要执行业务)
AuthCheck.authCheck();
//调用目标对象的方法(执行A对象即被代理对象的execute方法)
Object result = methodProxy.invokeSuper(proxy, args);
//记录日志数据(动态添加其他要执行业务)
Report.recordLog();
return result;
}else if("delete".equals(method.getName())){
//.....
return methodProxy.invokeSuper(proxy, args);
}
//如果不需要增强直接执行原方法
return methodProxy.invokeSuper(proxy, args);
}
}
public class A {
public void execute(){
"执行A的execute方法...");
}
}
//代理类
public class CGLibProxy implements MethodInterceptor {
/**
* 被代理的目标类
*/
private A target;
public CGLibProxy(A target) {
super();
this.target = target;
}
/**
* 创建代理对象
* @return
*/
public A createProxy(){
// 使用CGLIB生成代理:
// 1.声明增强类实例,用于生产代理类
Enhancer enhancer = new Enhancer();
// 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
enhancer.setSuperclass(target.getClass());
// 3.//设置回调函数,即一个方法拦截
enhancer.setCallback(this);
// 4.创建代理:
return (A) enhancer.create();
}
/**
* 回调函数
* @param proxy 代理对象
* @param method 委托类方法
* @param args 方法参数
* @param methodProxy 每个被代理的方法都对应一个MethodProxy对象,
* methodProxy.invokeSuper方法最终调用委托类(目标类)的原始方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//过滤不需要该业务的方法
if("execute".equals(method.getName())) {
//调用前验证权限(动态添加其他要执行业务)
AuthCheck.authCheck();
//调用目标对象的方法(执行A对象即被代理对象的execute方法)
Object result = methodProxy.invokeSuper(proxy, args);
//记录日志数据(动态添加其他要执行业务)
Report.recordLog();
return result;
}else if("delete".equals(method.getName())){
//.....
return methodProxy.invokeSuper(proxy, args);
}
//如果不需要增强直接执行原方法
return methodProxy.invokeSuper(proxy, args);
}
}
被代理的类无需接口即可实现动态代理,而CGLibProxy代理类需要实现一个方法拦截器接口MethodInterceptor并重写intercept方法,类似JDK动态代理的InvocationHandler接口,也是理解为回调函数,同理每次调用代理对象的方法时,intercept方法都会被调用,利用该方法便可以在运行时对方法执行前后进行动态增强。关于代理对象创建则通过Enhancer类来设置的,Enhancer是一个用于产生代理对象的类,作用类似JDK的Proxy类,因为CGLib底层是通过继承实现的动态代理,因此需要传递目标对象(如A)的Class,同时需要设置一个回调函数对调用方法进行拦截并进行相应处理,最后通过create()创建目标对象(如A)的代理对象,运行结果与前面的JDK动态代理效果相同。
类加载器织入
-指通过特殊的的类加载器,在虚拟机JVM加载字节码的时候进行织入,比如AspectWerkz(已并入AspecJ)及JBoss就使用这种方式。
编译器织入
静态织入 (AspectJ)
编译期织入
在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
分支主题
ajc编译器,是一种能够识别aspect语法的编译器
链接期(编译后)织入
将aspect类和java目标类同时编译成字节码文件后,再进行织入处理,这种方式比较有助于已编译好的第三方jar和Class文件进行织入操作
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给加锁,其他请求不可操作,面向增删改操作,不加锁可能出现一千个请求去查看修改一条数据.但是如果有写操作的情况,建议一定不能设置只读
软件性能
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
spring事务的核心接口
三个核心接口实现编程式事务
TransactionDefinition(定义事务(事务的规则))
代码
它包含了事务的静态属性,比如:事务传播行为、超时时间等等。
Spring 为我们提供了一个默认的实现类:DefaultTransactionDefinition
PlatformTransactionManager(事务管理器)(执行具体的事务操作(回滚或提交))
代码
DataSourceTransactionManager:适用于使用JDBC和iBatis进行数据持久化操作的情况。
HibernateTransactionManager:适用于使用Hibernate进行数据持久化操作的情况。
JpaTransactionManager:适用于使用JPA进行数据持久化操作的情况。
另外还有JtaTransactionManager 、JdoTransactionManager、JmsTransactionManager等等。
如果我们使用JTA进行事务管理,我们可以通过 JNDI 和 Spring 的 JtaTransactionManager 来获取一个容器管理的 DataSource。JtaTransactionManager 不需要知道 DataSource 和其他特定的资源,因为它将使用容器提供的全局事务管理。而对于其他事务管理器,比如DataSourceTransactionManager,在定义时需要提供底层的数据源作为其属性,也就是 DataSource。与 HibernateTransactionManager 对应的是 SessionFactory,与 JpaTransactionManager 对应的是 EntityManagerFactory 等等。
TransactionStatus(运行着的事务的状态)
PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。
public interface TransactionStatus{ boolean isNewTransaction(); void setRollbackOnly(); boolean isRollbackOnly();}
事务管理的意义
“按照给定的事务规则来执行提交或者回滚操作”。
“给定的事务规则”就是用 TransactionDefinition 表示的,
“按照……来执行提交或者回滚操作”便是用 PlatformTransactionManager
而 TransactionStatus 用于表示一个运行着的事务的状态。
编程式事务
直接使用PlatformTransactionManager来进行编程式事务管理
使用框架底层API(JJDBC,Session)来进行事务控制
代码
事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。
直接使用TransactionTemplate来进行编程式事务管理
代码
创建基于SavePoint的嵌套事务
声明式事务
配置方式
注解元数据驱动的声明式事务(@Transactional)
配置事务
在需要使用事务的类上添加注解即可 @Transactional
propagation:事务传播行为, isolation:事务隔离级别定义;默认为“DEFAULT” timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统; readonly:事务只读设置,默认为false,表示不是只读; rollbackfor:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚; norollbackfor:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
XML元数据驱动的声明式事务
基于 TransactionInterceptor和ProxyFactoryBean 的事务管理
配置
TransactionInterceptor的两个主要属性
transactionManager(指定一个事务管理器,并将具体事务相关的操作委托给它)
Properties 类型的 transactionAttributes 属性(来定义事务规则)
来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。
事务的规则(传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常])
传播行为是唯一必须设置的属性,其他都可以忽略,Spring为我们提供了合理的默认值。
传播行为的取值必须以“PROPAGATION_”开头,具体包括:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七种取值。
隔离级别的取值必须以“ISOLATION_”开头,具体包括:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五种取值。
如果事务是只读的,那么我们可以指定只读属性,使用“readOnly”指定。否则我们不需要设置该属性。
超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务任然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如“+RuntimeException”、“+tion”等等。
导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。
示例
<property name= *Service >PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20,+AbcException,+DefException,-HijException</property>
针对所有方法名以 Service 结尾的方法,使用 PROPAGATION_REQUIRED 事务传播行为,事务的隔离级别是 ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出 AbcException 或者 DefException 类型的异常,则仍然提交,当抛出 HijException 类型的异常时必须回滚事务。这里没有指定 readOnly ,表示事务不是只读的。
<property name= test >PROPAGATION_REQUIRED,readOnly</property>
针对所有方法名为 test 的方法,使用 PROPAGATION_REQUIRED 事务传播行为,并且该事务是只读的。除此之外,其他的属性均使用默认值。比如,隔离级别和超时时间使用底层事务性资源的默认值,并且当发生未检查异常,则回滚事务,发生已检查异常则仍提交事务。
配置好了 TransactionInterceptor,我们还需要配置一个 ProxyFactoryBean 来组装 target 和advice。这也是典型的 Spring AOP 的做法。通过 ProxyFactoryBean 生成的代理类就是织入了事务管理逻辑后的目标类。
配置文件太多。我们必须针对每一个目标对象配置一个 ProxyFactoryBean;另外,虽然可以通过父子 Bean 的方式来复用 TransactionInterceptor 的配置,但是实际的复用几率也不高;这样,加上目标对象本身,每一个业务类可能需要对应三个 <bean/> 配置,随着业务类的增多,配置文件将会变得越来越庞大,管理配置文件又成了问题。
基于TransactionProxyFactoryBean的配置 (Spring 经典的声明式事务管理)(TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一)
配置
基于<tx/>命名空间的配置
配置
<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,
并通过<tx:attributes>指定具体需要拦截的方法<tx:method>拦截方法,其中参数有: name:方法名称,
将匹配的方法注入事务管理,可用通配符 propagation:事务传播行为, isolation:事务隔离级别定义;
默认为“DEFAULT” timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读; rollback-for:需要触发回滚的异常定义,
可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不
导致事务回滚; no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求
自定义 --><aop:config> <aop:pointcut expression= execution(* com.kaizhi.*.service.impl.*.*(..))
id= pointcut /> <!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref= advice pointcut-ref= pointcut /></aop:config>
并通过<tx:attributes>指定具体需要拦截的方法<tx:method>拦截方法,其中参数有: name:方法名称,
将匹配的方法注入事务管理,可用通配符 propagation:事务传播行为, isolation:事务隔离级别定义;
默认为“DEFAULT” timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读; rollback-for:需要触发回滚的异常定义,
可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不
导致事务回滚; no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求
自定义 --><aop:config> <aop:pointcut expression= execution(* com.kaizhi.*.service.impl.*.*(..))
id= pointcut /> <!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref= advice pointcut-ref= pointcut /></aop:config>
如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。
利用切点表达式,一个配置可以匹配多个方法
基于BeanNameAutoProxyCreator的配置
配置
是个bean后处理器,BeanNameAutoProxyCreator是个根据bean名生成自动代理的代理创建器(可以跟自己创建的拦截器一起用)
MethodInterceptor 接口,在调用目标对象的方法时,就可以实现在调用方法之前、调用方法过程中、调用方法之后对其进行控制。
代码
ProxyFactory proxyFactory=new ProxyFactory();
//创建一个代理对象
proxyFactory.setTarget(new TestMethodInterceptor());
//设置代理对象的目标对象
proxyFactory.addAdvice(new adviseMethodInterceptor());
//设置代理对象的方法拦截器
Object proxy = proxyFactory.getProxy();
//得到生成的代理对象
TestMethodInterceptor methodInterceptor = (TestMethodInterceptor) proxy;
methodInterceptor.doSomeThing( 通过代理工厂设置代理对象,拦截代理方法 );
//调用方法
//创建一个代理对象
proxyFactory.setTarget(new TestMethodInterceptor());
//设置代理对象的目标对象
proxyFactory.addAdvice(new adviseMethodInterceptor());
//设置代理对象的方法拦截器
Object proxy = proxyFactory.getProxy();
//得到生成的代理对象
TestMethodInterceptor methodInterceptor = (TestMethodInterceptor) proxy;
methodInterceptor.doSomeThing( 通过代理工厂设置代理对象,拦截代理方法 );
//调用方法
配置
参数
beanNames
设置哪些bean需要自动生成代理
interceptorNames
指定事务拦截器,自动创建事务代理时,系统会根据这些事务拦截器的属性来生成对应的事务代理。
定义了两个org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator,按照顺序分别计作A,B,A会为目标对象T生成代理对象P1,B会为P1生成代理对象P2,代理逻辑就变成: P2代理P1,P1代理T,而不是P2、P1都代理T。所以,调用T的方法时,应该先执行P1,但P2代理了P1,最终先执行P2,再执行P1,在执行T。这种情况下,如果T的方法上有注解,只有P1能够拿到注解信息,P2是拿不到的。所以,不要定义个多个org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator的bean。
实现原理
AOP
PlatformTransactionManager
建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理.事务管理作为一种横切关注点,可以通过AOP方法模块化.Spring 通过 SpringAOP 框架支持声明式事务管理.
最细粒度只能作用到方法级别
@Transactional错误使用失效场景
1.@Transaction在private上:当标记在protected、private、package-visible方法上时,不会产生错误,但也不会表现出为它指定的事务配置。可以认为它作为一个普通方法参与到一个public方法的事务中。
2.@Transaction的事务传播方式配置错误
3.@Transaction注解属性rollbackFor设置错误:Spring默认抛出了未检查unchecked异常(继承自RuntimeException的异常)或者Error才回滚事务;其他异常不会触发回滚事务
4.同一个类中方法调用,导致@Transaction失效:由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理
5.异常被catch捕获导致@Transaction失效
6.数据库引擎不支持事务
SpringMVC
执行流程
1、 用户发送请求至前端控制器DispatcherServlet
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、 执行处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、 ViewReslover解析后返回具体View
10、DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户
结构图
运行原理
Spring如何解决循环依赖问题
Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给属性赋值阶段里面Spring会解析你的属性,并且赋值,当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿得到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完,一共执行了4次,至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中
概念
解决思路
收藏
0 条评论
下一页