本文整理的都是一些我个人认为比较有用 重要的知识
Spring概述
- 非侵入式: 基于Spring开发的应用中的对象可以不依赖于Spring的API
- 依赖注入: DI–Dependency Injection控制反转(IOC)最经典的实现
- 面向切面编程: Aspect OrientedProgramming AOP
- 容器: Spring是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化: Spring实现了使用简单的组件配置组合成一个复杂的应用,在Spring中可以使用XML和Java注解组合这些对象
- 一站式: 在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
IOC
目的:解耦合,实现每个组件时,只关注组件内部的事情
Inverion of Control 控制反转,一个对象依赖的对象会通过被动的方式传递进来
将你设计好的对象交给IOC容器控制,主要控制了外部资源获取
IOC不是一种技术,是一种思想,一个重要的面向对象的编程法则,它能指导出我们如何设计出松耦合,更优良的程序,传统应用程序都是由我们在类的内部主动创建依赖对象,从而导致类与类之间的高耦合,难于测试
IOC:把创建和查找依赖对象的控制权交给容器,由容器进行注入组合对象,所以对象与对象之间是松耦合的,也方便了测试,利于功能复用,使得程序的整个体系结构变得非常灵活
DI(Dependency Injection)依赖注入
IOC的另一种表述方式:即组件以一些预先定义好的方式(如:setter方法)接受来自于容器的资源注入,相对于IOC而言,这些表述更直接
组件之间依赖关系由容器动态的将某个依赖关系注入到组件之中,依赖注入的目的并非为软件系统带来更多的功能,而是为了提升组件重用的频率,并为系统搭建一个灵活,可扩展的平台
通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现,
注:
谁依赖谁:应用程序依赖于IOC容器
为什么需要依赖:应用程序需要IOC容器来提供对象需要的外部资源
谁注入谁:IOC容器注入应用程序某个对象,应用程序依赖的对象
注入了什么:注入某个对象所需的外部资源,包括对象,资源,常量数据
IOC容器的实现方式
BeanFactory : IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的
ApplicationContext : BeanFactory的子接口,提供了更多高级特性,面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory
ApplicationContext的主要实现类
ClassPathXmlApplicationContext : 对应类路径下的XML格式的配置文件
FileSystemXmlApplicationContext : 对应文件系统中的XML格式的配置文件
在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的
获取Bean
从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的
或者可以使用另外一个重载的方法,同时指定bean的id值和类型
Spring有两种类型的bean,一种是普通的bean,另一种是工厂bean,即FactoryBean
工厂bean跟普通bean不同,其返回的对象不是指定类型的一个实例,其返回的是该工厂bean的getObject方法所返回的对象
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。
Bean的配置
继承
- 继承可以从父bean中继承一些配置 但是id abstract autowire是不能被继承下来的
- 重复的属性可以写一个父bean 子bean通过继承来获得
- 继承后 例如公共属性 class就可以不需用在子bean中配置了
- 子bean也可以覆盖从父bean继承过来的配置
- 父bean可以作为配置模板,也可以作为bean实例。若只想把父bean作为模板,可以设置的abstract 属性为true,这样Spring将不会实例化这个bean
- 如果一个bean的class属性没有指定,则必须是抽象bean
- 并不是元素里的所有属性都会被继承。比如:autowire,abstract等。
- 也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同的属性配置。 但 此时abstract必须设为true。
bean之间的依赖
有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。例如:要求创建Employee对象的时候必须创建Department。 这里需要注意的是依赖关系不等于引用关系,Employee即使依赖Department也可以不引用它。
bean的作用域
在Spring中,可以在元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
- singleton: 单例的(默认值)在整个IOC容器中只能存在一个bean的对象
- prototype : 原型的 / 多例的 在整个IOC容器中可以有多个bean的对象,
- request : 每次HTTP请求都会创建一个新的bean, 该作用域仅适用于WebApplicatoinContext环境
- session : 同一个HTTP Session共享一个bean,不同的HTTP Session使用不同的bean,该作用域仅适用于WebApplicationContext环境
注: 当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象。
bean的后置处理器
bean的后置处理器允许在调用初始化方法前后对bean进行额外的处理
对IOC容器里的所有bean实例逐一处理,而非单一实例,其典型应用是:检查bean属性的正确性,或根据特定的标准改变bean的属性
后置处理器需要实现的接口
org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
postProcessBeforeInitialization(Object, String)
postProcessAfterInitialization(Object, String)
自动装配
手动装配: 以value或ref的方式明确指定属性值都是手动装配
自动装配: 根据指定的装配规则,不需要明确指定,spring自动将匹配的属性值注入bean中
装配模式
根据类型自动装配 : 将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,则不能执行自动装配
根据名称自动装配 : 必须将目标bean的名称和属性名设置的完全相同
通过构造器自动装配 : 当bean中存在多个构造器时,此种自动装配方式将会很复杂,不推
通过注解配置bean
相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。
使用注解标识组件
普通组件 : @Component
标识一个受Spring IOC 容器管理的组件
持久化层组件:@Repository
标识一个受Spring IOC容器管理的持久化层组件
业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件
表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
组件命名规则
默认情况: 使用组件的简单类名首字母小写后得到的字符串作为bean的id
使用组件注解的value属性指定bean的id
注意: 事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误,所以 @Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色。
组件装配
@Autowired 完成bean属性的自动装配
工作机制
- 首先会使用byType的方式进行自动装配,如果能唯一匹配则装配成功
- 如果匹配到多个兼容类型的bean,还会尝试使用byName的方式进行唯一确定
- 如果能唯一确定则装配成功 ,如果不能唯一确定,则装配失败,抛出异常
- 默认情况下,使用@Autowired标注的属性必须被装配,如果装配不了,也会抛出异常可以使用required = false 来设置不是必须要被装配
- 如果匹配到多个兼容类型的bean 可以使用@Qualifier来进一步指定要装配的bean的id值
- @Autowired @Qualifier 注解 既可以在成员变量上,也可以加在对应的set方法上
动态代理
代理设计模式的原理 : 使用一个代理将对象包装起来,然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理对象,代理对象决定是否以及何时将方法调用转到原始对象上
例如 : 我们在微信上跟微商买东西 最终给我们发货的是 真的的卖家, 微商只是一个代理, 代理的存在只是为了扩展原始对象的
动态代理的方式
基于接口实现动态代理:JDK动态代理
基于继承实现动态代理:cglib, javassist动态代理
实现步骤
JDK动态代理
创建一个目标对象
根据目标对象获取代理对象
根据类加载器帮助我们加载动态生成的代理类
传入类加载器,目标对象所有实现的接口,InvocationHandler接口对象
生成代理对象的时候 会把我们自己new的InvocationHandler对象传入代理类的构造方法中,构造方法中调用Proxy的构造方法, 把我们创建的赋值给父类的InvocationHandler
当我们使用代理对象调用接口的方法的时候,使用的是Proxy类的InvocationHandler对象 也就是我们自己创建的对象 调用invoke方法
invoke方法就是具体要执行的内容
最重要的一步就是在invoke方法中 目标对象执行目标方法
AOP(Aspect Oriented Programming)面向切面编程
优点:降低模块之间的耦合度,使系统容易扩展,更好的代码复用
AOP功能通常都和IOC容器一起使用,切面使用普通的bean定义语法来配置
在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就被模块化到特殊的类里,这样的类我们通常称之为 切面
术语
横切关注点: 从每个方法中抽取出来的同一类非核心业务
切面(Aspect): 封装横切关注点信息的类,每个关注点体现为一个通知方法
通知(Advice): 切面必须要完成的各个具体工作
目标(Target): 向目标对象应用通知之后创建的代理对象
连接点(Joinpoint): 横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置,例如,在某个方法调用前,调用后,方法捕获异常等
切入点(pointcut): 定位连接点的方式,每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物,如果把连接点看做数据库中的记录,那么切入点就是查询条件 AOP可以通过切入点定位到特定的连接点,切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
AspectJ
Java社区里最完整最流行的AOP框架
启用@AspectJ支持,<aop:aspectj-autoproxy>
@AspectJ声明一个切面
启用@AspectJ支持后,在配置文件中定义任意带有一个@AspectJ注解的bean都会被Spring自动识别并用于配置Spring AOP
注意: 在Spring AOP中,拥有切面的类本身不可能是其它切面中通知的目标,类上的@AspectJ标识为一个切面,并且从自动代理中排除它
声明一个切入点: Spring AOP只支持Spring bean的方法执行连接点
可以把切入点看做是Spring bean上方法执行的匹配
一个切入点声明有两部分:1、包含名字和任意参数的方法 2、切入点表达式,决定了我们关注那个方法的执行
切入点表达式使用@Pointcut注解来表示(作为切入点的方法必须返回void类型)
切入点指示符(PCD)的支持
execution:匹配方法执行的连接点
within:限定匹配特定类型的连接点
通用切入点表达式
任意公共方法的执行:execution(public * *(..))
任意一个名字以set开头的方法执行:execution(* set*(..))
A接口定义的任意方法的执行:execution(* cn.ljh.service.A.*(..))
在service包中定义的任意方法的执行:execution(* cn.ljh.service.*.*(..))
在service包中的任意连接点(在spring AOP中只是方法执行):within(cn.ljh.service.*)
声明通知
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或之后,或者前后运行,切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式
前置通知:@Before
在方法执行之前执行
后置通知:@AfterReturning
如果需要在通知内得到返回的值,设置returning属性在returning属性中使用的名字必须对应于通知方法内的一个参数名,当一个方法执行返回后,返回值作为相应的参数传入通知方法,一个returning子句也限制了只能匹配到返回指定类型值的方法
当使用后置通知时不允许返回一个完全不同的引用
异常通知:@AfterThrowing
通常想要限制通知只在某种特殊的异常被抛出 的时候匹配,还希望得到被抛出的异常,使用throwing属性不仅可以限制异常类型(如果不想限制,使用Throwable作为异常类型,还可以将异常绑定到参数上,必须和参数名一致
最终通知:After
不论一个方法是如何结束的,最终通知都会执行
必须准备处理异常返回和正常返回两种情况,通常用它来释放资源
环绕通知:@Around
经常在某线程安全的环境下,需要在一个方法执行之前和之后共享某种状态时使用
通知的第一个参数必须是ProceedingJoinPoint类型,在通知体内调用proceed()方法会导致后台的连接点方法执行.proceed()方法也可能被调用并且传入一个Object[]对象,该数组中的值将被作为方法执行时的参数,方法调用者得到的返回值就是环绕通知返回的值,
例: 一个简单的缓存切面,如果缓存中有值,就返回该值,否则调用proceed()方法,注:proceed可能在通知体内部被调用一次,许多次,或者根本不被调用,所有这些都是合法的
当前连接点细节
切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点,那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息
例如:当前连接点所在的方法名、当前传入的参数值等等,这些信息都封装在JoinPoint接口的实例对象中
JoinPoint
getArgs():获取实际参数数组
getSignature():封装签名信息的对象,可以进一步获取方法名
通知
在具体的连接点上要执行的操作
一个切面可以包括一个或多个通知
通知所使用的注解的值往往是切入点表达式
重用切入点定义
通过@Pointcut注解将一个切入点声明成简单的方法,切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的
切入点方法的访问控制符同时也控制着这个切入点的可见性,如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中,在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内,如果类没有与这个切面放在同一个包中,还必须包含包名。
指定切面的优先级
- 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级时不确定的
- 切面的优先级可以通过实现Ordered接口或利用@Order注解指定
- 实现Ordered接口,getOrder()方法的返回值越小,优先级越高
- 若使用@Order注解,序号出现在注解中
声明式事务
为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术
事务就是一组由逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要不都不执行
事务的资格关键属性(ACID)
原子性(atomicity): 原子的本意是"不可再分",事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可,事务的原子性要求事务中的所有操作要么都执行,要不都不执行
一致性(consistency): 一致指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
隔离性(isolation) : 在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
持久性(durability) : 持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
声明式事务
建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完目标方法之后根据执行情况提交或回滚事务
声明式事务最大的优点是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式) 便可以将事务规则应用到业务逻辑中
显然声明式事务管理要优于编程式事务管理,这正是Spring提倡的非侵入式的开发方式,声明式事务管理使业务代码不受污染,一个普通的pojo对象,只要加上注解就可以获得完全的事务支持,和编程式事务相比,声明式事务它的最细粒度只能作用到方法级别,无法做到像编程式事务那样作用到代码块级别
解决办法: 将需要今昔事务管理的代码块独立为方法
声明式事务管理的两种方式
基于tx和aop名称空间的xml配置文件
基于@Transactional注解
属性解释
name: 哪些方法需要有事务控制,支持 * 通配符
readonly=“boolean” 是否是只读事务
true: 告诉数据库此事务为只读事务,数据优化,会对性能有一定提升,所以只要使查询方法,建议使用此数据
false: 默认值,用于需要提交的事务, 新增 删除 修改
propagation: 事务的传播行为
当一个具有事务控制的方法被另一个有事务控制的方法调用后,需要如何管理事务
REQUIRED: 默认值,如果当前有事务,就在事务中执行,没有就新建一个事务
SUPPORTS: 如果当前有事务就在事务中执行,没有就在非事务状态下执行
MANDATORY: 必须在事务内部执行,如果当前有事务就在事务中执行,没有就报错
REQUIRES_NEW: 必须在事务中执行,如果当前没有事务新建事务,如果有就把当前事务挂起,新建一个事务
NOT_SUPPORTED: 必须在非事务下执行,如果当前没有事务,正常执行,如果有就把当前事务挂起,
NEVER: 必须在非事务下执行,如果没有事务,正常执行,如果有,报错
NESTED: 必须在事务状态下执行,如果没有事务新建事务,如果有,创建一个嵌套事务
isolation: 事务隔离级别,在多线程或并发访问下如何保证访问到的数据具有完整性
脏读: 一个事务(A)读取到另一个事务(B)中未提交的数据,另一个事务中数据可能进行了改变,此时A事务读取的数据可能和数据库中的数据是不一致的
不可重复度: 主要针对的是某行数据(或行中的某列),针对修改操作,两次读取在同一事务内,当事务A第一次读取数据后,事务B对事务A读取的数据进行修改,事务A中再次读取的数据不一致
幻读: 事务A按照特定条件查询出数据,事务B新增一条符合条件的数据,事务A中查询的数据和数据库中的数据不一致,主要针对新增和删除,两次事务的结果
DEFAULT: 默认值,由底层数据库自动判断应该使用声明隔离级别
READ_UNCOMMITTED: 可以读取未提交数据,可能出现脏读,不可重复,幻读,效率最高
READ_COMMITTED: 只能读取其他事务已提交数据,可以防止脏读,可能出现不可重复读,幻读
REPEATEBLE_READ: 读取的数据被添加锁,防止其它事务修改此数据,可以防止脏读,不可重复读,可能出现幻读
SERIALIZABLE: 排队操作,对整个表添加锁,一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表, 最安全,效率最低
事务超时:
一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务,单位秒,默认设置为底层事务系统的超时值,如果底层数据库事务没有设置超时值,那么就是none,没有超时限制
rollback-for="异常类型全限定路径"
当出现什么异常时需要进行回滚,建议给定该属性值,注:手动抛异常一定要给定该属性值
no-rollback-for="": 当出现什么异常时不回滚事务