官网:官网
官方下载地址:下载地址
GitHub地址& wps电脑版的下载的地方的方法#xff1a;源码
Spring是一个简化java企业级开发的一个轻量级的开源框架。内部包含了很多技术,比如:控制反转&依赖注入,AOP,Spring事务管理,还可以集成其他框架。
也叫IoC容器,是具有依赖注入功能的容器。主要负责容器中:对象的初始化,对象的实例化,对象与对象之间依赖关系的配置,对象的销毁,对外提供对象的查找等操作,对象的整个生命周期都是由容器来控制。
支持xml方式和java注解方式,在配置文件中列出需要让IoC管理的对象,在spring容器启动时会去加载这个配置文件,然后将这些对象组装好供外部访问者使用。
由spring管理的对象统称为Bean对象。就是普通的java对象,和使用者new的对象是一样的,只是这些对象由spring来创建和管理。
需要先在配置文件中定义好需要创建的Bean对象,这些配置统称为Bean定义配置元数据信息,spring容器通过读取这些配置元数据信息来构建和组装我们需要的对象。
1.5.1 BeanFactory 接口
spring容器的顶层接口,提供了容器最基本的功能。用BeanFactory加载配置文件时,不会创建容器里的对象,在获取(使用)对象的时候才会创建
常用的几个方法:
1.5.2 ApplicationContext 接口
继承了BeanFactory接口,包含BeanFactory的所有功能,并且进行了扩展,增加了许多企业级功能,如:AOP。国际化,事件支持等。加载xml的时候就会创建对象。
1.5.3 ClassPathXmlApplicationContext 类
实现了ApplicationContext接口,该实现类可以从classpath中加载beans.xml的配置,然后创建xml中需要的bean对象
1.5.4 AnnotationConfigApplicationContext 类
实现了ApplicationContext接口,该实现类可以加载用注解方式配置的Bean对象
2.1.1 beans.xml配置文件格式
(1) beans标签
beans是根元素,希迈纳可以包含任意数量的bean,import,alias元素
bean标签定义一个bean对象,格式如下:
每个bean都有一个名称,叫做bean名称。bean名称在spring中必须唯一,否则会报错,可以通过bean名称从spring容器中获取对应的对象。
(2) bean名称和别名定义规则
名称和别名可以通过bean元素中的id和name来定义,具体定义规则如下:
当id存在时,不管有没有name,取id为bean的名称。当id不存在时,此时看name,name的值可以通过**,;**或者空格分割,最后会按照分隔符得到一个String数组,数组的第一个元素作为bean的名称,其他的作为bean的别名当id和name都存在的时候,id为bean名称,name用来定义多个别名当id和name都不指定的时候,bean名称自动生成;生成规则是:bean名称为完整类名#编号,别名为完整的类名。
(3) alias元素
alias元素也可以用来给某个bean定义别名,语法:
(4) import元素
当我们的系统比较大的时候,会分成很多模块,每个模块会对应一个bean xml文件,我们可以在一个总的bean xml中对其他bean xml进行汇总,相当于把多个bean xml的内容合并到一个里面了,可以通过import元素引入其他bean配置文件。
2.2.1 scope(作用域)属性
singleton:表示这个bean是单例的。当scope为singleton时,spring容器在启动加载spring配置文件的时候,会将bean创建好放在容器中。整个spring容器中只存在一个bean实例,通过容器多次查找bean的时候(调用BeanFactory的getBean方法或者bean之间注入依赖的bean对象的时候),返回的都是同一个bean对象。有个特殊的情况,当bean的lazy被设置为true的时候,表示懒加载,那么使用的时候才会创建。prototype:表示这个bean是多例的,通过容器每次获取的bean都是不同的实例,每次获取时都会重新创建一个bean实例对象request:在spring容器的web环境中,每个http请求都会创建一个bean实例,request结束的时候,这个bean也就结束了session:在spring容器的web环境中,每个会话会对应一个bean实例,不同的session对应不同的bean实例application:一个web应用程序对应一个bean实例。但是,一个应用程序中可以创建多个spring容器,不同的容器中bean的名称不能相同。自定义作用域:自定义scope 3个步骤,实现Scope接口,将实现类注册到spring容器,使用自定义的sope
缺点:单例bean是整个应用共享的,所以需要考虑到线程安全问题多例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,会影响系统的性能
2.2.2 通过depend-on干预bean创建和销毁顺序
depend-on:设置当前bean依赖的bean名称,可以指定多个,多个之间可以用”,;空格“进行分割。
不管bean2,bean2,bean4在任何地方定义,会先将bean2,bean3,bean4在bean1创建之前创建好,表示bean1依赖于这3个bean;销毁的时候也会先销毁当前bean,再去销毁被依赖的bean,即先销毁bean1,再去销毁depend-on指定的bean2,bean3,bean4。
2.2.2 通过primary解决根据条件有多个匹配的bean
spring中可以通过bean元素的primary属性的primary="true"来解决这个问题,可以通过这个属性来指定当前bean为主要候选者,当容器查询一个bean的时候,如果容器中有多个候选者匹配的时候,此时spring会返回主要的候选者。
xml
2.2.3 通过autowire-candidate解决根据条件有多个匹配的bean
default:这个是默认值,autowire-candidate如果不设置,其值就是defaulttrue:作为候选者false:不作为候选者
xml
上面两种情形的分析:
容器在创建PrimartBean 的时候,发现其autowire为byType,即按类型自动注入,此时会在PrimartBean 类中查找所有setter方法列表,其中就包含了setService方法,setService方法参数类型是IService,然后就会去容器中按照IService类型查找所有符合条件的bean列表,此时容器中会返回满足IService这种类型并且autowire-candidate="true"的bean,刚才有说过bean元素的autowire-candidate的默认值是true,所以容器中符合条件的候选bean有2个:serviceA和serviceB,setService方法只需要一个满足条件的bean,此时会再去看这个列表中是否只有一个主要的bean,即bean元素的primary=“ture”的bean(而bean元素的primary默认值都是false),所以没有primary为true的bean,此时spring不知道选哪个,所以抛出NoUniqueBeanDefinitionException异常。
问题解决:
从上面过程中可以看出将某个候选bean的primary置为true或者只保留一个bean的autowire-candidate为true,将其余的满足条件的bean的autowire-candidate置为false就可以解决问题了。
2.2.4wps的的官网的下载网站是什么(wps下载电脑版链接打不开) bean延迟初始化和实时初始化(lazy-init)
在bean定义的时候通过lazy-init属性来配置bean是否是延迟加载,true:延迟初始化,false:实时初始化
2.2.4.1 实时初始化
在容器启动过程中被创建组装好的bean,称为实时初始化的bean,spring中默认都是实时初始化的bean,这些bean默认都是单例的,在容器启动过程中会被创建好,然后放在spring容器中以供使用
优点:
(1) 实时初始化的bean如果定义有问题,会在容器启动过程中会抛出异常,让开发者快速发现问题
(2) 容器启动完毕之后,实时初始化的bean已经完全创建好了,此时被缓存在spring容器中,当我们需要使用的时候,容器直接返回就可以了,速度是非常快的
2.2.4.2 延迟初始化
延迟初始化的bean在容器启动过程中不会创建,而是需要使用的时候才会去创建。
什么时候bean会被使用:
(1)被其他bean作为依赖进行注入的时候,比如通过property元素的ref属性进行引用,通过构造器注入、通过set注入、通过自动注入,这些都会导致被依赖bean的创建。
(2)调用容器的getBean方法获取bean
2.2.5 使用继承简化bean配置(abstract & parent)
当多个类中的属性配置完全一样是,可以进行简化。
通过继承优化代码:
将公共的代码提取出来,定义一个abstract="true"的属性,没有class,表示这个bean是抽象的,在spring容器中不会被创建,只是会将其当做bean定义的模板。从容器中查找abstract=true的bean的时候,会报错BeanIsAbstractException异常。bean的parent属性可以指定当前bean的父bean,子bean可以继承父bean中配置信息。子bean中也可自定义父bean中已经定义的配置,这样子会覆盖父bean中的配置信息。
2.2.6 单例bean中使用多例bean(lookup-method)
lookup-method:方法查找,可以对指定的bean的方法进行拦截,然后从容器中查找指定的bean作为被拦截方法的返回值
详见博客:Spring创建bean实例的常用四种方式
通过无参构造器创建bean实例通过set方法给bean的属性设置值和其他bean的引用将bean时实例传递给bean后置处理器postProcessBeforeInitialization调用配置好的bean的初始化方法将bean时实例传递给bean后置处理器postProcessAfterInitializationbean已创建调用配置好的bean的销毁方法
使用Spring前:所有对象的创建和组装都是使用者自己控制的。如果依赖多个对象的话,代码量较大,不方便维护,耦合度较高(依赖有调整时,改动比较大),不利于扩展
使用spring后:将对象创建和组装的主动控制权交给spring容器,使用者只需要去容器查找需要的对象就可以了。对象的构建过程被反转了,所以叫做控制反转。
IoC优点:IoC是面向对象编程中的一种设计原则,主要是为了降低系统代码的耦合度,让系统有利于扩展和维护。
指spring容器在创建对象时给其依赖对象设置值的方式。
3.2.1 手动注入(xml方式)
3.2.1.1 构造器注入
根据构造器参数索引注入
注意:通过参数索引注入对参数顺序有很强的依赖性,若构造函数参数位置被调整过,会导致注入出错;可以通过新增构造函数来解决。
2. 根据构造器参数类型注入
根据构造器参数名称注入
注意:关于参数名称可能不稳定的问题,spring提供了解决方案,通过ConstructorProperties注解来定义参数的名称,将这个注解加在构造方法上面,如下:
3.2.1.2 set方法注入
注意:setter注入相对于构造函数注入要灵活一些,构造函数需要指定对应构造函数中所有参数的值,而setter注入的方式没有这种限制,不需要对所有属性都进行注入,可以按需进行注入
3.2.1.3 注入容器中的bean
3.2.1.3.1 ref方式
3.2.1.3.2 内置bean方式wps官网下载入口在哪里
案例:
PersonModel
UserModel
CarModel
beans.xml
3.2.1.4 其他类型注入
注入java.util.List(list元素)
注入java.util.Set(set元素)
注入java.util.Map(map元素)
注入数组(array元素)
注入java.util.Properties(props元素)
注入null值或者空串
3.2.2 自动注入(xml方式和注解方式)
3.2.2.1 自动注入之xml方式
byName:会自动在容器上下文查找和自己对象里的属性的set方法后面的值相同的bean的id,需要保证IOC容器里bean的id要全局唯一byType:会自动在容器上下文查找和自己对象里的属性的set方法参数类型相同的bean,需要保证IOC容器里bean的类型要全局唯一constructor:判断当前构造器所有参数是否在容器中都可以找到匹配的bean对象,如果可以找到就使用这个构造器进行注入。default:根元素beans下有个default-autowire属性,这个属性可以批量设置当前文件中所有bean的自动注入的方式。
3.2.3.2 自动注入之注解方式
注意:spring全注解开发只需要在beans.xml中配置包扫描
3.2.3.2.1 @Autowired
@Autowired为spring的注解,默认通过ByType注入,当通过byType无法唯一确定bean的时候再通过byName查找.
当容器中有多个该对象时,通过ByType和byName都无法确定时,需要**@Qualifier**的value属性指定唯一的bean对象注入
3.2.3.2.2 @Resource
@Resource为java的注解,先通过ByName去容器中找,找不到再通过ByType类型去找;
当容器中有多个该对象时,通过ByType和byName都无法确定时,通过@Resource的name属性指定唯一的bean对象注入。
3.2.3.2.2 @Value
注意:spring4之后使用注解需要先导入aop依赖。
4.1.1 @Compent,@Repository,@Service,@Controller
@Component 意思是组件,加在类上说明这个类被spring管理了,相当于一个bean.
@Component 等价于
@Value 等价于 ,也可以加在set方法上
@Component的衍生注解:在web开发中,会按照mvc三层架构分层
dao:@Repositoryservice:@Servicecontroller:@Controller
注意:这四个注解的功能是一样的,都是将某个类注册到spring容器中,创建并装配bean
4.1.2 配置包扫描
4.1.2.1在 beans.xml配置注解扫描
4.1.2.2 包扫描的两种细节配置方式:
4.2.1 @Configuration和@Bean
JavaConfig是spring的一个子项目,在spring4之后,它成为了核心功能
等价于
@Configuration :代表这是一个配置类,和spring的beans.xml配置文件是等价的,这个注解配置的类会被spring托管,注册到容器中,也是一个@Component@Bean :注册一个bean,相当于beans.xml里面的,@Bean方法的名字相当于的id属性,方法的返回值相当于的class属性@Import :当有多个config配置类是。也可以通过@Import(类.class)进行组合@ComponentScan: 指定要扫描的包,该包下的注解就会生效通过AnnotationConfigApplicationContext来加载@Configuration注解修饰的类
4.2.2 spring中,类上加不加@Configuration注解,有什么区别?
@Configuration注解修饰的类,会被spring通过cglib做增强处理,通过cglib会生成一个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保创建的bean是单例的,多次调用时时同一个bean。不加该注解则生成的生成的时多例的bean。不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个bean注册到spring容器中只有当该配置类作为获取容器的方法AnnotationConfigApplicationContext的参数时,配置类上的@Configuration可以不写。如果配置类上都不加@Configuration注解,那么可以把这些配置类作为参数传到AnnotationConfigApplicationContext方法里。
案例:bean之间是有依赖关系
ServiceA
ServiceB
MyConfig
测试
结果:
1.有@Configuration
2.没有@Configuration
4.3.1 @ComponentScan和ComponentScans(bean批量注册)
@ComponentScan用于批量注册bean,spring会按照这个注解的配置,递归扫描指定包中的所有类,将满足条件的类批量注册到spring容器中可以通过value、basePackages、basePackageClasses 这几个参数来配置包的扫描范围可以通过useDefaultFilters、includeFilters、excludeFilters这几个参数来配置类的过滤器,被过滤器处理之后剩下的类会被注册到容器中指定包名的方式配置扫描范围存在隐患,包名被重命名之后,会导致扫描实现,所以一般我们在需要扫描的包中可以创建一个标记的接口或者类,作为basePackageClasses的值,通过这个来控制包的扫描范围@CompontScan注解会被ConfigurationClassPostProcessor类递归处理,最终得到所有需要注册的类
4.3.1.1 源码定义
常用参数:
value:指定需要扫描的包,如:com.javacode2018basePackages:作用同value;value和basePackages不能同时存在设置,可二选一basePackageClasses:指定一些类,spring容器会扫描这些类所在的包及其子包中的类nameGenerator:自定义bean名称生成器resourcePattern:需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件useDefaultFilters:对扫描的类是否启用默认过滤器,默认为trueincludeFilters:过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中excludeFilters:过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的类不会被注册到容器中lazyInit:是否延迟初始化被注册的bean@Repeatable(ComponentScans.class),这个注解可以同时使用多个
4.3.1.2 工作过程:
Spring会扫描指定的包,且会递归下面子包,得到一批类的数组然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
4.3.1.3 关键问题:
需要扫描哪些包?
通过value、backPackages、basePackageClasses这3个参数来控制过滤器有哪些?
通过useDefaultFilters、includeFilters、excludeFilters这3个参数来控制过滤器
4.3.1.4 扫描规则:
默认情况下,任何参数都不设置的情况下会将@ComponentScan修饰的类所在的包作为扫描包。
默认情况下,useDefaultFilters=true,spring容器内部会使用默认过滤器,规则是:凡是类上有@Repository、@Service、@Controller、@Component这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了。
4.3.1.5 案例1:任何参数未设置
分别在dao,controller,service包下创建类,用@Service,@Controller,@Repository注解标注
UserService
UserController
UserDao
UserModel
ScanBean
测试
使用AnnotationConfigApplicationContext作为ioc容器,将ScanBean.class作为参数传入,默认会扫描ScanBean类所在的包中的所有类,类上有@Component、@Repository、@Service、@Controller任何一个注解的都会被注册到容器中
4.3.1.6 案例2:指定需要扫描的包
指定需要扫毛哪些包,可以通过value或者basePackage来配置,二者选其一,都配置运行会报错,下面我们通过value来配置
测试结果
4.3.1.7 案例:basePackageClasses指定扫描范围
指定包名的方式扫描存在的一个隐患,若包被重名了,会导致扫描会失效,我们可以在需要扫描的包中定义一个标记的接口或者类,他们的唯一的作用是作为basePackageClasses的值,其他没有任何用途。
定义一个类或者接口
4.3.1.8 includeFilters和excludeFilters的使用
是一个Filter类型的数组,多个Filter之间为或者关系,即满足任意一个就可以了,看一下Filter的代码:
主要参数:
type:过滤器的类型,是个枚举类型,5种类型
ANNOTATION:通过注解的方式来筛选候选者,即判断候选者是否有指定的注解
ASSIGNABLE_TYPE:通过指定的类型来筛选候选者,即判断候选者是否是指定的类型
ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式
REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配
CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断
value:和参数classes效果一样,二选一
classes:3种情况如下
当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解
当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型
当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口
pattern:2种情况如下
当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
当type=FilterType.REGEX时,通过pattern来自正则表达式的值
4.3.1.8.1 扫描包含注解的类
我们自定义一个注解,让标注有这些注解的类自动注册到容器中
创建一个类,使用这个注解标注
再来一个类,使用spring中的标注
再来一个类,使用@CompontentScan标注
测试用例
结果
问题:Service1上标注了@MyBean注解,被注册到容器了,但是没有标注@MyBean啊,怎么也被注册到容器了?
回答:@CompontentScan注解中的useDefaultFilters默认是true,表示会启用默认的过滤器,默认的过滤器会将标注有@Component、@Repository、@Service、@Controller这几个注解的类也注册到容器中。
修改扫描代码:
如果我们只想将标注有@MyBean注解的bean注册到容器,需要将默认过滤器关闭,即:useDefaultFilters=false
再输出:
4.3.1.8.2 包含指定类型的类
被扫描的类满足IService.class.isAssignableFrom(被扫描的类)条件的都会被注册到spring容器中
@ComponentScan(
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,classes = IService.class)}
)
接口
实现类
@CompontentScan标注的类
测试:
4.3.1.8.3 自定义Filter
步骤:
1.设置@Filter中type的类型为:FilterType.CUSTOM
2.自定义过滤器类,需要实现接口:org.springframework.core.type.filter.TypeFilter
3.设置@Filter中的classses为自定义的过滤器类型
TypeFilter这个接口的定义:
是一个函数式接口,包含一个match方法,方法返回boolean类型,有2个参数,都是接口类型的,下面介绍一下这2个接口
MetadataReader接口:
类元数据读取器,可以读取一个类上的任意信息,如类上面的注解信息、类的磁盘路径信息、类的class对象的各种信息,spring进行了封装,提供了各种方便使用的方法。
MetadataReader接口的定义:
MetadataReaderFactory接口:
类元数据读取器工厂,可以通过这个类获取任意一个类的MetadataReader对象
MetadataReaderFactory接口定义:
案例:
需求:我们来个自定义的Filter,判断被扫描的类如果是IService接口类型的,就让其注册到容器中。
接口
实现类
自定义的TypeFilter类:
@CompontentScan标注的类
4.3.1.9 @ComponentScan重复使用
4.3.2 @Import使用:bean的批量注册
@Import可以用来批量导入需要注册的各种类,如普通的类、配置类,然后完成普通类和配置类中所有bean的注册。
@Import可以使用在任何类型上,通常情况下,类和注解上用的比较多。
value:一个Class数组,设置需要导入的类,可以是@Configuration标注的列,可以是ImportSelector接口或者ImportBeanDefinitionRegistrar接口类型的,或者需要导入的普通组件类。
当使用AnnotationConfigApplicationContext(MyConfig.class)加载配置类创建容器时,方法参数为某个主配置类的字节码文件,此时主配置类上可以不用加@Configuration注解。当有多个创建bean的配置类时,通过**@Import**注解在主配置类上进行导入其他配置类,此时其他配置类上也可以不加@Configuration注解。
源码:
@Import的value常见的有5种用法:
value为普通的类
通过@Import导入的2个类,bean名称为完整的类名,可以使用@Compontent注解指定被导入类的bean名称
value为@Configuration标注的类
value为@CompontentScan标注的类
项目中分多个模块,每个模块有各自独立的包,我们在每个模块所在的包中配置一个@CompontentScan类,然后通过@Import来导入需要启用的模块
第一个组件的包及里面的类:
第二个组件的包及里面的类:
总配置类:通过@Import导入每个模块中的组件扫描类
value为ImportBeanDefinitionRegistrar接口类型
这个接口提供了通过spring容器api的方式直接向容器中注册bean
importingClassMetadata:AnnotationMetadata类型的,通过这个可以获取被@Import注解标注的类所有注解的信息。
registry:BeanDefinitionRegistry类型,是一个接口,内部提供了注册bean的各种方法。
importBeanNameGenerator:BeanNameGenerator类型,是一个接口,内部有一个方法,用来生成bean的名称。
BeanDefinitionRegistry接口:bean定义注册器,提供了bean注册的各种方法,基本上所有bean工厂都实现了这个接口,让bean工厂拥有bean注册的各种能力,AnnotationConfigApplicationContext类也实现了这个接口
BeanNameGenerator接口:bean名称生成器
这个接口spring有三个内置的实现:
DefaultBeanNameGenerator:默认bean名称生成器,xml中bean未指定名称的时候,默认就会使用这个生成器,默认为:完整的类名#bean编号
AnnotationBeanNameGenerator:注解方式的bean名称生成器,比如通过@Component(bean名称)的方式指定bean名称,如果没有通过注解方式指定名称,默认会将完整的类名作为bean名称。
FullyQualifiedAnnotationBeanNameGenerator:将完整的类名作为bean的名称
BeanDefinition接口:bean定义信息,用来表示bean定义信息的接口,我们向容器中注册bean之前,会通过xml或者其他方式定义bean的各种配置信息,bean的所有配置信息都会被转换为一个BeanDefinition对象,然后通过容器中BeanDefinitionRegistry接口中的方法,将BeanDefinition注册到spring容器中,完成bean的注册操作。
案例:
Service1:
Service2:需要注入Service1
定义一个类实现ImportBeanDefinitionRegistrar接口,然后在里面实现上面2个类的注册
定义配置类导入实现类
测试:
value为ImportSelector接口类型
ImportSelector接口:
案例:
普通类:Service1
@Configuration标注的配置类:Module1Config
自定义一个ImportSelector,然后返回上面2个类的名称
@Import标注的类,导入MyImportSelector
测试
value为DeferredImportSelector接口类型
DeferredImportSelector是ImportSelector的子接口,既然是ImportSelector的子接口,所以也可以通过@Import进行导入。和ImportSelector不同地方有两点:延迟导入和指定导入的类的处理顺序。
延迟导入:
@Import的value包含了多个普通类、多个@Configuration标注的配置类、多个ImportSelector接口的实现类,多个ImportBeanDefinitionRegistrar接口的实现类,还有DeferredImportSelector接口实现类,此时spring处理这些被导入的类的时候,会将DeferredImportSelector类型的放在最后处理,会先处理其他被导入的类,其他类会按照value所在的前后顺序进行处理
案例:
来3个配置类,每个配置类中都通过@Bean定一个string类型的bean,内部输出一句文字。
Configuration1:
Configuration2:
Configuration3:
一个ImportSelector实现类,导入Configuration1
一个DeferredImportSelector实现类,导入Configuration2
一个总的配置类
测试及输出:
输出的结果结合一下@Import中被导入的3个类的顺序,可以看出DeferredImportSelector1是被最后处理的,其他2个是按照value中所在的先后顺序处理的。
指定导入的类的处理顺序:
@Import中有多个DeferredImportSelector接口的实现类时候,可以指定他们的顺序,指定顺序常见2种方式:
实现Ordered接口的方式:
实现Order注解的方式:
案例:
2个配置类,内部都有一个@Bean标注的方法,用来注册一个bean
Configuration1:
Configuration2:
来2个DeferredImportSelector实现类,分别来导入上面2个配置文件,顺便通过Ordered接口指定一下顺序,DeferredImportSelector1的order为2,DeferredImportSelector2的order为1,order值越小优先级越高
DeferredImportSelector1:
DeferredImportSelector2:
来个总的配置类,引入上面两个ImportSelector
MainConfig8:
测试输出:
4.3.2.1 案列
需求:
凡是类名中包含service的,调用他们内部任何方法,我们希望调用之后能够输出这些方法的耗时
实现分析:
我们可以通过代理来实现,bean实例创建的过程中,我们可以给这些bean生成一个代理,在代理中统计方法的耗时,这里面有2点:wps office免费版下载的网址怎么找(金山wps官网电话)
创建一个代理类,通过代理来间接访问需要统计耗时的bean对象拦截bean的创建,给bean实例生成代理生成代理
具体实现:
Service1
Service2
创建统计耗时的代理类:用cglib来实现一个代理类
拦截bean实例的创建,返回代理对象
需要将MethodCostTimeProxyBeanPostProcessor注册到容器中才会起作用,下面我们通过@Import结合ImportSelector的方式来导入这个类,将其注册到容器中
来一个@Import来导入MethodCostTimeImportSelector,下面我们使用注解的方式,在注解上使用@Import
来一个总的配置类
上面使用了@CompontentScan注解,此时会将Servce1和Service2这两个类注册到容器中wps官网下载的网址怎么找。
@1:此处使用了@EnableMethodCostTime注解,而@EnableMethodCostTime注解上使用了@Import(MethodCostTimeImportSelector.class),此时MethodCostTimeImportSelector类中的MethodCostTimeProxyBeanPostProcessor会被注册到容器,会拦截bean的创建,创建耗时代理对象。
测试:
结果:
如果我们不想开启方法耗时统计,只需要将MainConfig6上的@EnableMethodCostTime去掉就可以了
spring中有很多类似的注解,以@EnableXXX开头的注解,基本上都是通过上面这种方式实现的,如:
4.3.3 @Conditional 通过条件控制Bean的注册
请浏览另一篇博客:@Conditional注解的详解和应用
4.3.3 @Import使用:bean的批量注册
关于注解定义的详解,浏览博客:Java注解的定义和使用以及spring对注解的增强
5.1.1 什么是代理模式?
为其它对象提供一个代理以控制对这个对象的访问,在某些情况下,一个对象不适合或不能直接访问另一个对象,而代理可以在客户类与目标对象之间起到中介的作用。 具有这种访问关系呈现出来的模式称之为代理模式
5.1.2 代理的作用
功能增强:在原有功能的基础上增加了额外的功能控制访问:代理类不让客户能直接访问目标对象
5.2.1 静态代理
(1) 含义:
静态代理类是手工创建的一个java类,所要代理的目标是确定的。代理类包含了目标对象,在对目标对象的方法进行调用时可以进行功能增强。
(2)图示
代理类B、C和目标类D、E均实现了接口F中抽象方法出售U盘的功能。但厂家D、E不向个人客户A直接出售,只向代理商家B、C出售。某一天,F新增了U盘的某功能,类B、C、D、E均要修改
(3)角色分析
抽象角色(租房):一般是接口或者抽象类真实角色(房东):被代理的角色代理角色(中介):代理真实角色,可以做扩展操作,比如看房子,收中介费等客户:访问代理对象的人
(4)优缺点
静态代理模式好处:
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务公共业务交给代理角色,实现业务的分工公共业务发生扩展的时候,方便集中管理 静态代理模式缺点:
为了个性化增强,每一个真实的角色就会产生一个代理角色,代码量会翻倍,开发效率会变低当接口变化时,代理类和被代理类都要相应变化。
(5)demo展示
接口类
真实角色类 实现 接口
代理角色类 代理 真实角色类
如果UserServiceITwompl也实现了UserService,那么也要编写UserServiceTwoImplProxy
客户类
5.2.2 动态代理
(1)含义
在程序执行过程中,JDK利用反射机制创建对象的能力创建代理对象,不用创建代理类。代理目标不是确定的,而是可活动的,变化的。
动态代理支持在系统运行期给类动态添加代理,然后通过操控代理类完成对目标类的调用
(2)分类
动态代理分为两大类:基于接口的动态代理:JDK动态代理 :
使用java反射包java.lang.reflect中的类和接口实现动态代理的功能类:Proxy,Method接口:InvocationHandler 基于类的动态代理:cglib动态代理:
第三方的开源项目,高效的code生成类库,可以在运行期间扩展java类,实现java接口,被广泛的AOP框架使用
使用JDK动态代理,要求目标类与代理类实现相同接口。若目标类没实现接口,则不能使用该方法实现。此时可以考虑使用cgLib。cgLib代理的生成原理是生成目标类的子类,子类对象即代理对象。因此目标类不能是final的。
(3)优缺点
动态代理模式好处:
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务公共业务交给代理角色,实现业务的分工公共业务发生扩展的时候,方便集中管理一个动态代理类,可以代理实现了同一个接口的多个类当在接口中修改方法时,不用修改代理类JDK动态代理要求代理目标必须实现接口
在实际开发中,将日志、事务等与业务方法无关、但很多业务方法都要执行的代码放在InvocationHandler类invoke方法的功能增强中,这样一来:
解耦合,使业务代码与非业务代码分离,业务类专注于业务的实现减少代码复用
(5)demo
接口类
目标接口,定义目标类实现的功能
目标类
JDK动态代理要求代理目标必须实现接口
动态代理类
创建InvocationHandler的实现类,在invoke方法中实现代理功能
主要步骤:
创建动态代理的目标对象通过构造方法或者set方法注入目标对象,首选构造方法注入生成动态代理类对象,并将返回值转为接口类型调用应用处理程序的invoke方法处理代理实例,如有返回值,接收返回结果,可进行强制类型转换;在invoke实现功能增强(日志、事务等)
注意:
invoke方法是java.lang.reflect.InvocationHandler中的方法,此方法实现了对原有目标对象的业务方法的调用,即目标类的方法的调用,并在此基础上可以选择进行一定的功能增强。此方法的参数均为JDK管理,不需要人工传入:
Object proxy:目标类的代理对象Method method:目标类的业务方法对象Object[] args:目标类的业务方法所需的参数(例如本例业务方法没有参数,args为空)
创建动态代理类Proxy对象的参数:
第一个参数是目标类的类加载器,可看作固定写法第二个参数是目标类的接口对象,可看作固定写法,正是因为这个参数决定了JDK动态代理必须实现接口第三个参数是包含目标类的MyInvocationHandle 对象
客户类调用步骤:
创建目标对象创建InvocationHandle对象,构造器参数决定了代理目标,传谁代理谁创建代理对象,并将返回值强转为接口类型,强转成功的原因是目标对象target实现了 UserService接口实现代理的业务功能
5.3.1 概述
在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能:
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面
定义:AOP(Aspect Oriented Programming 面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
目的:是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中,从而减少系统的重复代码量,并且能够降低模块间的耦合度,有利于程序代码的扩展和维护,降低了维护成本,提高了开发的效率。
交叉业务逻辑:是指通用的、与主业务逻辑无关,却被各个业务模块大量调用代码,如安全检查、 事务、日志、缓存等。
5.3.2 AOP的相关术语
横切关注点:跨越应用程序多个模块的方法或者功能。如:日志记录、缓存、安全检查、事务处理等。切面(Aspect):横切关注点被模块化的对象,指封装好的用于插入业务逻辑功能的类,是对业务逻辑的一种增强连接点(Joinpoint):指被切面织入的具体方法。通常业务接口中所有可以增强的方法均为连接点。Spring中一个连接点表示一个方法的调用切入点(Pointcut):一个或者多个连接点的集合,定义了切面在何处执行,切点也是一个连接点,也指满足匹配规则的一组类或者方法通知/增强(Advice):在切面类中定义的方法,也是在切点处要执行的代码,通知定义了通知代码切入到目标代码的时间点,是在目标代码之前执行还是之后执行,通知类型不同切入时间不同目标(Target):将要被增强的对象。即包含主业务逻辑的类的对象代理(Proxy):目标对象增强后创建的代理对象;织入(Wearving):把切面应用到目标对象并创建新的代理对象的过程,切面在指
定的连接点被织入到目标对象中。
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-timeweaving,LTW)就支持以这种方式织入切面运行期:切面在应用运行的某个时刻被织入。一般情况下,在织
入切面时,AOP容器会为目标对象动态地创建一个代理对象。
Spring AOP就是以这种方式织入切面的 引入(Introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
5.3.3 AOP实现方式
5.3.3.1 AOP的技术实现框架:
Spring:Spring的API实现较为笨重,一般用在事务处理aspectJ: 一个开源,专门做AOP的框架,隶属于Eclipse基金会
5.3.3.2 AspectJ 对 AOP 的实现
aspectJ框架实现AOP有两种方式:
使用xml配置文件,一般用于配置全局事务使用注解,一般在项目开发中使用这种方式
(1) AspectJ 的通知类型
AspectJ 中常用的通知有五种类型,体现在五个不同的添加在切面的注解:
前置通知:@Before后置通知:@AfterReturning、环绕通知:@Around、异常通知:@AfterThrowing、最终通知:@After
(2) AspectJ 的切入点表达式
① execution (* com.sample.service.impl…*. *(…))
execution(): 表达式主体。第一个星号:表示返回类型, 星号表示所有的类型。包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包。第二个*号:表示类名,*号表示所有的类。*(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数② 通过方法签名定义切点
execution(public * *(…)) 匹配所有目标类的public方法
③ 通过类定义切点
execution(* com.baobaotao.Waiter.(…)) 匹配Waiter包下所有类的所有方法,参数不限
execution( com.baobaotao.Waiter+.(…)) 匹配Waiter接口及其所有实现类的方法
④ 通过类包定义切点
在类名模式串中,“.”表示包下的所有类,而“…”表示包、子孙包下的所有类
execution( com.baobaotao.(…)) 匹配com.baobaotao包下所有类的所有方法
execution( com.baobaotao…(…)) 匹 配com.baobaotao包、子孙包下所有类的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及 com.baobaotao.dao.user包下的所有类的所有方法都匹配。“…”出现在类名中时,后面必须跟“”,表示包、子孙包下的所有类
切入点表达式 要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。
在其中可以使用以下符号:
*:0至多个任意字符
… : 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包
+:用在类名后,表示当前类及其子类;用在接口名后,表示当前接口及其实现类
(3) AspectJ 的开发环境
(4) beans.xml加入aop的约束
(5) AspectJ 的开启注解支持
注解支持的参数:
proxy-target-class=“false” JDK动态代理proxy-target-class=“true” cglib动态代理
(6) AspectJ 的通知注解
切面类:@Aspect 注解的类叫做切面类
通知注解:在切面类中修饰方法的注解,这些注解体现了通知类型。通知注解修饰的方法称之为通知方法,所有的通知方法都可包含JoinPoint类型参数
@Before前置通知
在目标方法执行之前执行。被注解为前置通知的方法,JoinPoint类型的参数的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等
@AfterReturning后置通知-注解有 returning 参数
在目标方法执行之后执行,可以获取到目标方法的返回 值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 继承于JoinPoint,因此可以根据它获取方法的信息。其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行;
ProceedingJoinPoint能获取连接点方法的定义,参数等信息
@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象;在效果上相当于一个try…catch语句。目标方法的方法体在try语句块中,而切面方法的方法体放在了catch子句中@After最终通知
无论目标方法是否抛出异常,该增强均会被执行。在执行效果上,相当于将切面方法的方法体放在了try…catch…finally…语句发finally子句中。@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。 方法体内部也无需添加代码。
有多个增强类对同一个方法进行增强时,设置增强类优先级@Order(数值类型),数值越小优先级越高。
5.3.4 AOP具体实现方式
5.3.4.1 使用spring的API接口实现【主要是Spring API】
接口
创建目标类
通过spring API 创建要切入的通知
创建beans.xml
测试
测试结果
5.3.4.2 使用自定义方式实现AOP【主要是切面】
接口
被切入的目标类
自定义切面
beans.xml
测试
测试结果
5.3.4.3 使用注解的AOP实现
接口
目标类
切面
beans.xml
测试
测试结果
Spring事务相关:Spring事务的实现和面试常问情形
事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败,所有操作都失败。
事务的四个特性:
原子性:事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生
一致性:事务开始之前和事务结束以后,数据库的完整性约束没有被破坏
持久性:一个事务一旦提交,它对数据库中的数据的改变就是永久性的
隔离性:数据库存在多个事务同时操作时,保证各事务之间互不干扰,操作互不影响
7.1.1 事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。PlatformTransactionManager 接口有两个常用的实现类:
➢ DataSourceTransactionManager:JdbcTemplate 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager: Hibernate 进行持久化数据时使用。
7.1.2 Spring 的回滚方式
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生编译时异常时提交.
运行时异常:是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理。只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。
编译时异常:即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如 SQLException,ClassNotFoundException,IOException 等都属于编译时异常。 RuntimeException 及其子类以外的异常,均属于编译时异常。
7.1.3 事务定义接口
事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。
1.定义了五个事务隔离级别常量
事务的隔离级别:定义一个事务可能受其他并发事务活动影响的程度,事务的隔离级别可以想象为这个事务处理数据的自私程度。
如果不考虑事务的隔离性,在事务的并发操作中可能会出现脏读,不可重复读,幻读:
脏读(Dirty read):一个事务读取了被另一个事务修改但未提交的数据时。如果这些数据稍后被回滚了,那么第一个事务读取的数据就会是无效的
不可重复读(Nonrepeatable read):一个事务多次执行相同的查询,但每次查询结果都不相同。这通常是因为另一个并发事务在两次查询之间更新了该条数据并提交。不可重复读重点在修改
幻读(Phantom reads):当一个事务读取几行记录后,另一个并发事务插入或者删除了一些记录。在后来的查询中,第一个事务看到原来没有的记录。幻读重点在新增或删除
区别:
脏读和不可重复读的区别:脏读是某一事务读取了另一个事务未提交的脏数据;而不可重复读则是在同一个事务范围内多次查询同一条数据却返回了不同的数据,这是由于在查询间隔期间,该条数据被另一个事务修改并提交了。
幻读和不可重复读区别:幻读和不可重复读都是读取了另一个事务中已经提交的数据,不同的是不可重复读多次查询的都是同一个数据项,针对的是对同一行数据的修改;而幻读针对的是一个数据整体(如:数据条数),主要是新增或者删除
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别:
隔离级别含义ISOLATION_DEFAULT采用 DB 默认的事务隔离级别ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读ISOLATION_READ_COMMITTED(Oracle 默认级别)允许从已经提交的并发事务读取,保证了一个事务不会读到另一个并行事务已修改但未提交的数据。可防止脏读,但幻读和不可重复读仍可能会发生。ISOLATION_REPEATABLE_READ(MYSQL默认级别)可重复读,保证一个事务不能读取另一个事务已修改但未提交的数据并且在开始读取数据时,不再允许其他事务进行修改操作。可防止脏读和不可重复读,但幻读仍可能发生ISOLATION_SERIALIZABLE完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是性能最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。
并发性能:Read Uncommitted > Read Committed > Repeatable Read > Serializable
隔离级别:Read Uncommitted < Read Committed < Repeatable Read < Serializable
隔离级别越低,并发性能越高
2.定义了七个事务传播行为常量
所谓事务传播特性就是多个事务方法相互调用时,事务如何在这些方法间传播。一般用在事务嵌套的场景。
如:A类中的事务方法 add()调用B类中的事务方法update(),在调用执行期间事务的维护情况,就称为事务传播行为。
传播特性含义PROPAGATION_REQUIRED如果外层有事务,则当前事务加入到外层事务,内外部方法均属于同一个事务,一块提交,一块回滚。如果外层没有事务,内部方法为自己新开启一个事务,且内部方法的事务之间互不干扰。出现异常时只回滚自己,外层方法不会回滚PROPAGATION_REQUIRES_NEW如果外层有事务,内部方法每次都会单独新开启一个事务,同时把外层事务挂起,当内部事务执行完毕,恢复外围事务的执行,内部事务抛出异常会被外部事务感知,自己捕获后只回滚自己则不会被外围事务感知。内外部事务之间,内部事务和方法之间互相独立互不干扰。如果外层没有开启事务,执行当前内部方法新开启的事务,且内部的事物之间互不干扰,出现异常时只回滚自己PROPAGATION_SUPPORTS如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务PROPAGATION_NOT_SUPPORTED该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码PROPAGATION_NEVER该传播机制不支持外层事务,即如果外层有事务就抛出异常PROPAGATION_MANDATORY如果外层没有事务,则抛出异常PROPAGATION_NESTED外围事务未开启时,作用与REQUIRED相同,内部方法都会新开启自己的事务,并且互不干扰。外围开启事务时,内部事务是外部事务的子事务,外围事务回滚,子事务一定回滚;同时内部方法抛出异常回滚,外围事务感知异常导致整体事务回滚。还有一点,内部方法事务是外围的一个子事务,有单独的保存点,所以方法抛出异常捕获后可以单独回滚,不影响外围主事务和其他子事务
注意:什么是事务挂起操作?
可以这样理解,对当前存在事务的现场生成一个快照,然后将事务现场清理干净,然后重新开启一个新事物,新事物执行完毕之后,将事务现场清理干净,在根据前面快照恢复旧事务
1.开启注解驱动
2.声明事务管理器
3.业务层 public 方法加入事务属性的注解
@Transactional 的所有可选属性如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。若只有 一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务
1.添加Maven依赖
2.声明事务管理器
3.配置事务通知
4.配置增强器