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