spring
2018-06-19 10:28:16 0 举报
AI智能生成
spring核心内容
作者其他创作
大纲/内容
概念
Spring 是一个开源的轻量级 Java SE(Java 标准版本)/Java EE(Java 企业版本)开发应用框架,其目的是用于
简化企业级应用程序开发
最根本的使命
简化开发
而他主要是通过:面向 Bean、依赖注入以及面向切面这三种方式来达成的
面向 Bean
Spring 提供了 IOC 容器通过配置文件或者注解的方式来管理对象之间的依赖关
系
依赖注入
BeanFactory 支持两个对象模型
1,单例:模型提供了具有特定名称的对象的共享实例,可以在查询时对其进行检索。Singleton 是
默认的也是最常用的对象模型。对于无状态服务对象很理想。
2,原型:模型确保每次检索都会创建单独的对象。在每个用户都需要自己的对象时,原型模型最适
合。
面向切面
面向切面编程,即 AOP,是一种编程思想,它允许程序员对横切关注点或横切典型的职责分界线的
行为(例如日志和事务管理)进行模块化。AOP 的核心构造是方面(切面),它将那些影响多个类的行
为封装到可重用的模块中。
系统架构
核心容器
由 spring-beans、spring-core、spring-context 和 spring-expression( Spring
n Expression , Language, SpEL) 4 个模块组成。
AOP 和设备支持
由 spring-aop、spring-aspects 和 spring-instrumentation 3 个模块组成
数据访问及集成
由 spring-jdbc、spring-tx、spring-orm、spring-jms 和 spring-oxm 5 个模
块组成
Web
由 spring-web、spring-webmvc、spring-websocket 和 spring-webmvc-portlet 4 个模块组
成。
报文发送
即 spring-messaging 模块
Test
即 spring-test 模块。
Spring IOC 体系结构
BeanFactory
Spring Bean 的创建是典型的工厂模式,这一系列的 Bean 工厂,也即 IOC 容器为开发者管理对象
间的依赖关系提供了很多便利和基础服务,在 Spring 中有许多的 IOC 容器的实现供用户选择和使用,
其相互关系如下
在 BeanFactory 里只对 IOC 容器的基本行为作了定义,根本不关心你的 bean 是如何定义怎样加载的。
正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心
在 BeanFactory 里只对 IOC 容器的基本行为作了定义,根本不关心你的 bean 是如何定义怎样加载的。
正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。
而要知道工厂是如何产生对象的,我们需要看具体的 IOC 容器实现,Spring 提供了许多 IOC 容器的
实现。比如 XmlBeanFactory,ClasspathXmlApplicationContext 等。其中 XmlBeanFactory 就是针对最
基本的 IOC 容器的实现,这个 IOC 容器可以读取 XML 文件定义的 BeanDefinition(XML 文件中对 bean
的描述),如果说 XmlBeanFactory 是容器中的屌丝,ApplicationContext 应该算容器中的高帅富.
ApplicationContext 是 Spring 提供的一个高级的 IOC 容器,它除了能够提供 IOC 容器的基本功能
外,还为用户提供了以下的附加服务。
从 ApplicationContext 接口的实现,我们看出其特点:
1. 支持信息源,可以实现国际化。(实现 MessageSource 接口)
2. 访问资源。(实现 ResourcePatternResolver 接口,这个后面要讲)
3. 支持应用事件。(实现 ApplicationEventPublisher 接口)
BeanDefinition
SpringIOC 容器管理了我们定义的各种 Bean 对象及其相互的关系,Bean 对象在 Spring 实现中是以
BeanDefinition 来描述的,其继承体系如下
Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵
活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通
过下图中的类完成
IOC 容器的初始化
IOC 容器的初始化包括 BeanDefinition 的 Resource 定位、载入和注册这三个基本的过程
ApplicationContext 系列容器也许是我们最熟悉的,因为 web 项目中
使用的 XmlWebApplicationContext 就属于这个继承体系,还有 ClasspathXmlApplicationContext 等,
其继承体系如下图所示
IOC 容器初始化的基本步骤
1.初始化的入口在容器实现中的 refresh()调用来完成
2.对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition
3.然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 SpringIOC 的服务了
IOC 容器的依赖注入
依赖注入发生的时间
当 Spring IOC 容器完成了 Bean 定义资源的定位、载入和解析注册以后,IOC 容器中已经管理类 Bean
定义的相关数据,但是此时 IOC 容器还没有对所管理的 Bean 进行依赖注入
(1).用户第一次通过 getBean 方法向 IOC 容索要 Bean 时,IOC 容器触发依赖注入
(2).当用户在 Bean 定义资源中为<Bean>元素配置了 lazy-init 属性,即让容器在解析注册 Bean 定义时
进行预实例化,触发依赖注入
高级特性
lazy-init 属性对 Bean 预初始化
FactoryBean 产生或者
修饰 Bean 对象的生成
BeanPostProcessor 后置处理器的实现
BeanPostProcessor 后置处理器是 Spring IOC 容器经常使用到的一个特性,这个 Bean 后置处理器是一
个监听器,可以监听容器触发的 Bean 声明周期事件。后置处理器向容器注册以后,容器中管理的 Bean
就具备了接收 IOC 容器事件回调的能力
autowiring 自动装配功能
beanFactoryPostprocess beanPostprocess
Spring AOP 设计原理及具体实践
AOP 的相关概念
目标对象(Target Object) ) :被一个或者多个切面所通知的对象。例如,AServcieImpl 和 BServiceImpl,当然在实
际运行时,Spring AOP 采用代理实现,实际 AOP 操作的是 TargetObject 的代理对象。
AOP 理 代理( (AOP Proxy ):在 Spring AOP 中有两种代理方式,JDK 动态代理和 CGLIB 代理。默认情况下,TargetObject
实现了接口时,则采用 JDK 动态代理,例如,AServiceImpl;反之,采用 CGLIB 代理,例如,BServiceImpl。强制
使用 CGLIB 代理需要将 <aop:config>的 proxy-target-class 属性设为 true
切面(Aspect ):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。“切面”在
ApplicationContext 中<aop:aspect>来配置
连接点( (Joinpoint ):程序执行过程中的某一行为,例如,MemberService .get 的调用或者 MemberService .delete
抛出异常等行为。
通知(Advice) ) :“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个“Advice”。
前置通知(Before advice ):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
ApplicationContext 中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect 中的 doBefore 方法。
后置通知( (After advice) ): :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext
中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,ServiceAspect 中的 returnAfter 方法,所以 Teser 中调用
UserService.delete 抛出异常时,returnAfter 方法仍然执行
返回后通知( (After return advice) ): :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext
中在<aop:aspect>里面使用<after-returning>元素进行声明。
环绕通知( (Around advice) ): :包围一个连接点的通知,类似 Web 中 Servlet 规范中的 Filter 的 doFilter 方法。可以在
方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在<aop:aspect>里面使用<aop:around>
元素进行声明。例如,ServiceAspect 中的 around 方法。
抛出异常后通知( (After throwing advice) ): :在方法抛出异常退出时执行的通知。ApplicationContext 中在<aop:aspect>
里面使用<aop:after-throwing>元素进行声明。例如,ServiceAspect 中的 returnThrow 方法。
切入点( (Pointcut ) :匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。切面中的所有通知所关注的连
接点,都由切入点表达式来决定
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
throws-pattern?
modifiers-pattern:方法的操作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除 ret-type-pattern 和 name-pattern 之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示
com.spring.service 包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。
Transaction
什么是事务
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)
特点
事务是恢复和并发控制的基本单位
4 个属性
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都
不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原
子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对
并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据
的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务的基本原理
Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring 是无法提供事务功
能的
Spring 事务的传播属性
所谓 spring 事务的传播属性,就是定义在存在多个事务同时存在的时候,spring 应该如何处理这些事
务的行为。这些属性在 TransactionDefinition 中定义,具体常量的解释见下表
假设外层事务 Service A 的 Method A() 调用 内层 Service B 的 Method B()
PROPAGATION_REQUIRED
支持当前事务,如果当前没有事务,就新建一个事
务。这是最常见的选择,也是 Spring 默认的事务
的传播。
如果 ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA()
的时候 spring 已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运
行在 ServiceA.methodA() 的事务内部,就不再起新的事务。
假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。
PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
新建的事务将和被挂起的事务没有任何关系,是两
个独立的事务,外层事务失败回滚之后,不能回滚
内层事务执行的结果,内层事务失败抛出异常,外
层事务捕获,也可以不处理回滚操作
比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事
务级别为 PROPAGATION_REQUIRES_NEW。
那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,
ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。
他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一
个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA()
失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的
异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看 B 抛出的异常是不
是 A 会回滚的异常)。
PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方
式执行。
假设 ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到 ServiceB.methodB()
时,如果发现 ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现
ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最
外层的事务。
PROPAGATION_MANDATORY
支持当前事务,如果当前没有事务,就抛出异常
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把
当前事务挂起。
PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异
常。
PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套的事
务中。如果没有活动事务,则按 REQUIRED 属性执
行。它使用了一个单独的事务,这个事务拥有多个
可以回滚的保存点。内部事务的回滚不会对外部事
务造成影响。它只对
DataSourceTransactionManager 事务管理器起效
现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时
两者之间又将如何协作呢?ServiceB#methodB 如果 rollback, 那么内部事务(即
ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下
两种处理方式
a.捕获异常,执行异常分支逻辑
这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败,
那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不
会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而
PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。
b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么
首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即
ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback
数据库隔离级别
隔离级别
隔离级别的值
导致的问题
Read-Uncommitted
0
导致脏读
Read-Committed
1
避免脏读,允许不可重复读和幻读(默认的)
Repeatable-Read
2
避免脏读,不可重复读,允许幻读
Serializable
3
串行化读,事务只能一个一个执行,避免了脏读、
不可重复读、幻读。执行效率慢,使用时慎重
脏读
一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这
时候回滚了,那么第二个事务就读到了脏数据。
不可重复读
一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进
行了修改,这时候两次读取的数据是不一致的。
幻读
第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一
个事务就会丢失对新增数据的修改
总结
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle
少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB
Spring中的隔离级别
ISOLATION_DEFAULT
这是个 PlatfromTransactionManager 默认的
隔离级别,使用数据库默认的事务隔离级别。另外
四个与 JDBC 的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED
这是事务最低的隔离级别,它充许另外一个事务可
以看到这个事务未提交的数据。这种隔离级别会产
生脏读,不可重复读和幻像读
ISOLATION_READ_COMMITTED
保证一个事务修改的数据提交后才能被另外一个
事务读取。另外一个事务不能读取该事务未提交的
数据。
ISOLATION_REPEATABLE_READ
这种事务隔离级别可以防止脏读,不可重复读。但
是可能出现幻像读
ISOLATION_SERIALIZABLE
这是花费最高代价但是最可靠的事务隔离级别。事
务被处理为顺序执行
Spring MVC 框架设计原理
Spring MVC 请求处理流程
①:DispatcherServlet 是 springmvc 中的前端控制器(front controller),负责接收 request 并将 request
转发给对应的处理组件.
②:HanlerMapping 是 springmvc 中完成 url 到 controller 映射的组件.DispatcherServlet 接收 request,
然后从 HandlerMapping 查找处理 request 的 controller.
③:Cntroller 处理 request,并返回 ModelAndView 对象,Controller 是 springmvc 中负责处理 request
的组件(类似于 struts2 中的 Action),ModelAndView 是封装结果视图的组件.
④ ⑤ ⑥:视图解析器解析 ModelAndView 对象并返回对应的视图给客户端
Spring MVC 的工作机制
在容器初始化时会建立所有url 和controller 的对应关系,保存到Map<url,controller>中
这样就可以根据 request 快速定位到 controller
因为最终处理 request 的是 controller 中的方法,Map
中只保留了 url 和 controller 中的对应关系,所以要根据 request 的 url 进一步确认 controller 中的 method,
这一步工作的原理就是拼接 controller 的 url(controller 上@RequestMapping 的值)和方法的 url(method 上
@RequestMapping 的值),与 request 的 url 进行匹配,找到匹配的那个方法
确定处理请求的method后,接下来的任务就是参数绑定,把request 中参数绑定到方法的形式参数上,
这一步是整个请求处理过程中最复杂的一个步骤。springmvc 提供了两种 request 参数与方法形参的绑定
方法:
① 通过注解进行绑定,@RequestParam
② 通过参数名称进行绑定.
使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("a"),就可以将 request 中参数 a 的
值绑定到方法的该参数上.使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java 反射只提
供了获取方法的参数的类型,并没有提供获取参数名称的方法.springmvc解决这个问题的方法是用asm框
架读取字节码文件,来获取方法的参数名称.asm 框架是一个字节码操作框架,关于 asm 更多介绍可以参考
它的官网.个人建议,使用注解来完成参数绑定,这样就可以省去 asm 框架的读取字节码的操作.
Spring MVC 源码分析
其一,ApplicationContext 初始化时建立所有 url 和 controller 类的对应关系(用 Map 保存);
其二,根据请求 url 找到对应的 controller,并从 controller 中找到处理请求的方法;
其三,request 参数绑定到方法的形参,执行方法处理请求,并返回结果视图.
Spring MVC 的优化
1.controller如果能保持单例,尽量使用单例,这样可以减少创建对象和回收对象的开销.也就是说,如果
controller 的类变量和实例变量可以以方法形参声明的尽量以方法的形参声明,不要以类变量和实例变量
声明,这样可以避免线程安全问题
2.处理 request 的方法中的形参务必加上@RequestParam 注解,这样可以避免 springmvc 使用 asm 框
架读取 class 文件获取方法参数名的过程.即便 springmvc 对读取出的方法参数名进行了缓存,如果不要读
取 class 文件当然是更加好.
3.阅读源码的过程中,发现 springmvc 并没有对处理 url 的方法进行缓存,也就是说每次都要根据请求
url 去匹配 controller 中的方法 url,如果把 url 和 method 的关系缓存起来,会不会带来性能上的提升呢?有
点恶心的是,负责解析 url 和 method 对应关系的 ServletHandlerMethodResolver 是一个 private 的内部类,
不能直接继承该类增强代码,必须要该代码后重新编译.当然,如果缓存起来,必须要考虑缓存的线程安全
问题.
0 条评论
下一页