spring简介之不求甚解

来源:互联网 发布:出身不好知乎 编辑:程序博客网 时间:2024/05/21 08:56

1. Spring框架的功能


  • 清理:Spring是轻量级的,基本的版本大小为2MB


  • 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。


  • 面向切面的编程AOP:Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。


  • 容器:Spring包含并管理应用中对象的生命周期和配置


  • MVC框架: Spring-MVC


  • 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务JTA


  • 异常处理:Spring提供方便的API把具体技术相关的异常


2. Spring的七大模块


  • Spring Core: 核心容器提供 Spring 框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。


  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。


  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。


  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。


  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。


  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。


  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。


3. Spring容器


Sping的容器可以分为两种类型:

  1. BeanFactory:(org.springframework.beans.factory.BeanFactory接口定义)是最简答的容器,提供了基本的DI支持。最常用的BeanFactory实现就是XmlBeanFactory类,它根据XML文件中的定义加载beans,该容器从XML文件读取配置元数据并用它去创建一个完全配置的系统或应用。


  2. ApplicationContext应用上下文:(org.springframework.context.ApplicationContext)基于BeanFactory之上构建,并提供面向应用的服务。


4. ApplicationContext通常的实现


  • ClassPathXmlApplicationContext:从类路径下的XML配置文件中加载上下文定义,把应用上下文定义文件当做类资源。


  • FileSystemXmlApplicationContext:读取文件系统下的XML配置文件并加载上下文定义。


  • XmlWebApplicationContext:读取Web应用下的XML配置文件并装载上下文定义。


ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");


5. IOC & DI


Inversion of Control, 一般分为两种类型:依赖注入DI(Dependency Injection)和依赖查找(Dependency Lookup).依赖注入应用比较广泛。


Spring IOC扶着创建对象,管理对象(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。


优点:把应用的代码量降到最低。容器测试,最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。


DI依赖注入是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用床架对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述组件需要哪些服务,之后一个IOC容器辅助把他们组装起来。


IOC的注入方式:1. 构造器依赖注入;2. Setter方法注入。


6. 如何给spring容器提供配置元数据


XML配置文件

基于注解的配置

基于Java的配置@Configuration, @Bean


7. bean标签中的属性:


id

name

class


init-method:Bean实例化后会立刻调用的方法


destory-method:Bean从容器移除和销毁前,会调用的方法


factory-method:运行我们调用一个指定的静态方法,从而代替构造方法来创建一个类的实例。


scope:Bean的作用域,包括singleton(默认),prototype(每次调用都创建一个实例), request,session, global-session(注意spring中的单例bean不是线程安全的)


autowired:自动装配 byName, byType, constructor, autodetect(首先阐释使用constructor自动装配,如果没有发现与构造器相匹配的Bean时,Spring将尝试使用byType自动装配)


8. beans标签中相关属性


default-init-method

default-destory-method


default-autowire:默认为none,应用于Spring配置文件中的所有Bean,注意这里不是指Spring应用上下文,因为你可以定义多个配置文件


9. Bean的生命周期


Bean的构造

调用setXXX()方法设置Bean的属性

调用BeanNameAware的setBeanName()

调用BeanFactoryAware的setBeanFactory()方法

调用BeanPostProcessor的postProcessBeforeInitialization()方法

调用InitializingBean的afterPropertiesSet()方法

调用自定义的初始化方法

调用BeanPostProcessor类的postProcessAfterInitialization()方法

调用DisposableBean的destroy()方法

调用自定义的销毁方法


10. Spring中注入集合


允许值相同

不允许值相同

键和值都可以为任意类型,key, key-ref, value-ref, value可以任意搭配


XXX键和值都只能是String类型


11. 装配空值


<property name="xxx"><null/></property>


12. 自动装配(autowiring)


有助于减少甚至消除配置和元素,让Spring自动识别如何装配Bean的依赖关系。<context:annotation-config/>


与之对应的是:自动检测(autodiscovery),比自动装配更近了一步,让Spring能够自动识别哪些类需要被配置成SpringBean,从而减少对元素的使用。<context:component-scan>


13. 注解


Spring容器默认禁用注解装配。最简单的开启方式<context:annotation-config/>。

Spring支持的几种不同的用于自动装配的注解:


Spring自带的@Autowired注解

JSR-330的@Inject注解

JSR-250的@Resource注解


14. @Autowired


@Autowired具有强契约特征,其所标注的属性或参数必须是可装配的。如果没有Bean可以装配到@Autowired所标注的属性或参数中,自动装配就会失败,抛出NoSuchBeanDefinitionException.


属性不一定非要装配,null值也是可以接受的。在这种场景下可以通过设置@Autowired的required属性为false来配置自动装配是可选的,如:


@Autowired(required=false)

private Object obj;


注意required属性可以用于@Autowired注解所使用的任意地方。但是当使用构造器装配时,只有一个构造器可以将@Autowired的required属性设置为true。其他使用@Autowired注解所标注的构造器只能将required属性设置为false。此外,当使用@Autowired标注多个构造器时,Spring就会从所有满足装配条件的构造器中选择入参最多的那个构造器。


可以使用@Qualifier明确指定要装配的Bean.如下:


@Autowired

@Qualifier("objName")

private Object obj;


15. 自定义的限定器


@Target({ElementType.FIELF, ElementType.PARAMETER, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @Interface SpecialQualifier{}


此时,可以通过自定义的@SpecialQualifier注解来代替@Qualifier来标注,也可以和@Autowired一起使用:


@Autowired

@SpecialQualifier

private Object obj;


此时,Spring会把自动装配的范围缩小到被@SpecialQualifier标注的Bean中。如果被@SpecialQualifier标注的Bean有多个,我们还可以通过自定义的另一个限定器@SpecialQualifier2来进一步缩小范围。


16. @Autowired优缺点


Spring的@Autowired注解是减少Spring XML配置的一种方式。但是它的类会映入对Spring的特定依赖(即使依赖只是一个注解)。


17. @Inject


和@Autowired注解一样,@Inject可以用来自动装配属性、方法和构造器;与@Autowired不同的是,@Inject没有required属性。因此@Inject注解所标注的依赖关系必须存在,如果不存在,则会抛出异常。


18. @Named


相对于@Autowired对应的Qualifier,@Inject所对应的是@Named注解。


@Inject

@Named("objName")

private Object obj;


19. SpEL表达式


语法形式在#{}中使用表达式,如:


<property name="count" value="#{5}"/>


20. @Value


@Value是一个新的装配注解,可以让我们使用注解装配String类型的值和基本类型的值,如int, boolean。我们可以通过@Value直接标注某个属性,方法或者方法参数,并传入一个String类型的表达式来装配属性,如:


@Value("Eruption")

private String song;


@Value可以配合SpEL表达式一起使用,譬如有些情况下需要读取properties文件中的内容,可以使用:


@Value("#{configProperties['ora_driver']}")


详细可以参考Spring+Mybatis多数据源配置(三)——Spring如何获取Properties文件的信息


21. 自动检测Bean


<context:component-scan>元素除了完成与<context:annotation-config>一样的工作,还允许Spring自动检测Bean和定义Bean.<context:component-scan>元素会扫描指定的包和其所有子包,如下:


<context:component-scan base-package="com.zzh.dao" />


22. 为自动检测标注Bean


默认情况下,查找使用构造型(stereotype)注解所标注的类,这些特殊的注解如下:

- @Component:通用的构造型注解,标志此类为Spring组件

- @Controller:标识将该类定义为SpringMVC controller

- @Repository:标识将该类定义为数据仓库

- @Service:标识将该类定义为服务


以@Component为例:


@Component

public class Guitar implements Intrument{}


这里@Component会自动注册Guitar 为Spring Bean,并设置默认的Bean的Id为guitar,首字母大写变小写。注意如果第一个和第二个字母都是大写,默认的Bean的id会有特殊处理。

也可以指定Bean的Id如:


@Component("guitarOne")

public class Guitar implements Intrument{}


23. AOP


面向切面的编程AOP,是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。


AOP的核心是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在SpringAOP中,切面通过带有@Aspect注解的类实现。


关注点是应用中的一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。


横切关注点一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。


连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。


切点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。


引入运行我们在已存在的类中添加新的方法和属性。


24. AOP通知


通知是个在方法执行前后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码


Spring切面可以应用五种类型的通知:

  • before:前置通知,在一个方法执行前被调用。@Before

  • after: 在方法执行之后调用的通知,无论方法执行是否成功。@After

  • after-returning: 仅当方法成功完成后执行的通知。@AfterReturning

  • after-throwing: 在方法抛出异常退出时执行的通知。@AfterThrowing

  • around: 在方法执行之前和之后调用的通知。@Around


25. Spring的事务类型


  1. 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。


  1. 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。


26. ACID


  • Atomic原子性:事务是由一个或多个活动所组成的一个工作单元。原子性确保事务中的所有操作全部发生或者全部不发生。


  • Consistent一致性:一旦事务完成,系统必须确保它所建模的业务处于一致的状态


  • Isolated隔离性:事务允许多个用户对象头的数据进行操作,每个用户的操作不会与其他用户纠缠在一起。


  • Durable持久性:一旦事务完成,事务的结果应该持久化,这样就能从任何的系统崩溃中恢复过来。



28. JTA事务


如果你的事务需要跨多个事务资源(例如:两个或多个数据库;或者如Sping+ActiveMQ整合需要将ActiveMQ和数据库的事务整合起来),就需要使用JtaTransactionManager:


<bean id="jtaTransactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"/>


JtaTransactionManager将事务管理的职责委托给了一个JTA的实现。JTA规定了应用程序与一个或多个数据源之间协调事务的标准API。transactionManagerName属性指明了要在JNDI上查找的JTA事务管理器。


JtaTransactionManager将事务管理的职责委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象。通过UserTransaction.commit()方法来提交事务。类似地,如果事务失败,UserTransaction的rollback()方法将会被调用。


29. 声明式事务


1. TransactionDefinition接口中定义五个隔离级别:


ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应;


ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。


ISOLATION_READ_COMMITTED  保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。


ISOLATION_REPEATABLE_READ  这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。


ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。


1: Dirty reads(脏读)。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。


2: non-repeatable reads(数据不可重复读)。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。


3: phantom reads(幻象读数据),这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name=”ppgogo*”,第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由”dd”改成”ppgogo1″,结果取出来了7个数据。


2. 在TransactionDefinition接口中定义了七个事务传播行为:


(1)PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA{ 

…… 

methodB(); 

…… 

}

 

//事务属性 PROPAGATION_REQUIRED 

methodB{ 

   …… 

}


使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。


单独调用methodB方法:


Java代码


main{ 

metodB(); 

}


相当于

Java代码


Main{ 

Connection con=null; 


try{ 


con = getConnection(); 

con.setAutoCommit(false); 

//方法调用

methodB(); 

//提交事务

con.commit(); 


Catch(RuntimeException ex){ 

            //回滚事务

con.rollback();   

finally{ 

//释放资源

closeCon(); 

}


Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。


单独调用MethodA时,在MethodA内又会调用MethodB.


执行效果相当于:


Java代码


main{ 

Connection con = null; 

try{

con = getConnection(); 

    methodA(); 

con.commit(); 

catch(RuntimeException ex){ 

con.rollback(); 

}  

finally{ 

   closeCon(); 

}  

}


调用MethodA时,环境中没有事务,所以开启一个新的事务.当在MethodA中调用MethodB时,环境中已经有了一个事务,所以methodB就加入当前事务。


(2)PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA(){ 

  methodB(); 

}

 

//事务属性 PROPAGATION_SUPPORTS 

methodB(){ 

  …… 

}


单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。


(3)PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA(){ 

   doSomeThingA(); 

methodB(); 

doSomeThingB(); 

}

 

//事务属性 PROPAGATION_REQUIRES_NEW 

methodB(){ 

   …… 

}


Java代码:


main(){ 

  methodA(); 

}


相当于

Java代码:


main(){ 

  TransactionManager tm = null; 

try{ 

  //获得一个JTA事务管理器 

    tm = getTransactionManager(); 

    tm.begin();//开启一个新的事务 

    Transaction ts1 = tm.getTransaction(); 

    doSomeThing(); 

    tm.suspend();//挂起当前事务 

    try{ 

      tm.begin();//重新开启第二个事务 

      Transaction ts2 = tm.getTransaction(); 

      methodB(); 

      ts2.commit();//提交第二个事务 

   } 

  Catch(RunTimeException ex){ 

      ts2.rollback();//回滚第二个事务 

  } 

  finally{ 

     //释放资源 

   } 

    //methodB执行完后,复恢第一个事务 

    tm.resume(ts1); 

doSomeThingB(); 

    ts1.commit();//提交第一个事务 

catch(RunTimeException ex){ 

   ts1.rollback();//回滚第一个事务 

finally{ 

   //释放资源 

}


在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。


(5)PROPAGATION_NOT_SUPPORTED  总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。(代码示例同上,可同理推出)


(6)PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常;


(7)PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而nestedTransactionAllowed属性值默认为false;


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA(){ 

   doSomeThingA(); 

   methodB(); 

   doSomeThingB(); 

}

 

//事务属性 PROPAGATION_NESTED 

methodB(){ 

  …… 

}


如果单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,相当于下面的效果:


Java代码:


main(){ 

Connection con = null; 

Savepoint savepoint = null; 

try{ 

   con = getConnection(); 

   con.setAutoCommit(false); 

   doSomeThingA(); 

   savepoint = con2.setSavepoint(); 

   try{ 

       methodB(); 

   }catch(RuntimeException ex){ 

      con.rollback(savepoint); 

   } 

   finally{ 

     //释放资源 

  }

 

   doSomeThingB(); 

   con.commit(); 

catch(RuntimeException ex){ 

  con.rollback(); 

finally{ 

   //释放资源 

}


当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。


嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。


PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。


使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不同的支持方式。


PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。


另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务,  它是已经存在事务的一个真正的子事务. 潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。


由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.

PROPAGATION_REQUIRED应该是我们首先的事务传播行为。它能够满足我们大多数的事务需求。



32. 配置HandlerMapping


Spring自带了多个处理器映射实现:


  • BeanNameUrlHandlerMapping:根据控制器Bean的名字将控制器映射到URL。

  • ControllerBeanNameHandlerMapping:与BeanNameUrlHandlerMapping类似,根据控制器Bean的名字将控制器映射到URL。使用该处理器映射实现,Bean的名字不需要遵循URL的约定。

  • ControllerClassNameHandlerMapping:通过使用控制器的类名作为URL基础将控制器映射到URL。

  • DefaultAnnotationHandlerMapping:将请求映射给使用@RequestingMapping注解的控制器和控制器方法。

  • SimpleUrlHandlerMapping:使用定义在Spring应用上下文的熟悉集合将控制器映射到URL。


使用如上这些处理器映射通常只需在Spring中配置一个Bean。如果没有找到处理器映射Bean,DisapatchServlet将创建并使用BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。我们一般使用基于注解的控制器类。


<mvc:annotation-driven />

   <bean id="defaultAnnotationHandlerMapping"

       class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">

   </bean>


在构建控制器的时候,我们还需要使用注解将请求参数绑定到控制器的方法参数上进行校验以及信息转换。提供注解驱动的特性。


33. 配置HandlerAdapter


<bean id="annotationMethodHandlerAdapter"

class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />


34. 配置视图


在SpringMVC中大龄使用了约定优于配置的开发模式。InternalResourceViewResolver就是一个面向约定的元素。它将逻辑视图名称解析为View对象,而该对象将渲染的任务委托给Web应用程序上下文中的一个模板。


<!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->

   <bean

       class="org.springframework.web.servlet.view.InternalResourceViewResolver">

       <property name="viewClass"

           value="org.springframework.web.servlet.view.JstlView" />

       <property name="prefix" value="/WEB-INF/jsp/" />

       <property name="suffix" value=".jsp" />

   </bean>


当DispatcherServlet要求InternalResourceViewResolver解析视图的时候,它将获取一个逻辑视图名称,添加”/WEB-INF/jsp/”前缀和”.jsp”后缀。等待的结果就是渲染输出的JSP路径。在内部,InternalResourceViewResolver接下来会将这个路径传递给View对象,View对象将请求传递给JSP.

1. TransactionDefinition接口中定义五个隔离级别:


ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应;


ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。


ISOLATION_READ_COMMITTED  保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。


ISOLATION_REPEATABLE_READ  这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。


ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。


1: Dirty reads(脏读)。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。


2: non-repeatable reads(数据不可重复读)。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。


3: phantom reads(幻象读数据),这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name=”ppgogo*”,第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由”dd”改成”ppgogo1″,结果取出来了7个数据。


2. 在TransactionDefinition接口中定义了七个事务传播行为:


(1)PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA{ 

…… 

methodB(); 

…… 

}

 

//事务属性 PROPAGATION_REQUIRED 

methodB{ 

   …… 

}


使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。


单独调用methodB方法:


Java代码


main{ 

metodB(); 

}


相当于

Java代码


Main{ 

Connection con=null; 


try{ 


con = getConnection(); 

con.setAutoCommit(false); 

//方法调用

methodB(); 

//提交事务

con.commit(); 


Catch(RuntimeException ex){ 

            //回滚事务

con.rollback();   

finally{ 

//释放资源

closeCon(); 

}


Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。


单独调用MethodA时,在MethodA内又会调用MethodB.


执行效果相当于:


Java代码


main{ 

Connection con = null; 

try{

con = getConnection(); 

    methodA(); 

con.commit(); 

catch(RuntimeException ex){ 

con.rollback(); 

}  

finally{ 

   closeCon(); 

}  

}


调用MethodA时,环境中没有事务,所以开启一个新的事务.当在MethodA中调用MethodB时,环境中已经有了一个事务,所以methodB就加入当前事务。


(2)PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA(){ 

  methodB(); 

}

 

//事务属性 PROPAGATION_SUPPORTS 

methodB(){ 

  …… 

}


单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。


(3)PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA(){ 

   doSomeThingA(); 

methodB(); 

doSomeThingB(); 

}

 

//事务属性 PROPAGATION_REQUIRES_NEW 

methodB(){ 

   …… 

}


Java代码:


main(){ 

  methodA(); 

}


相当于

Java代码:


main(){ 

  TransactionManager tm = null; 

try{ 

  //获得一个JTA事务管理器 

    tm = getTransactionManager(); 

    tm.begin();//开启一个新的事务 

    Transaction ts1 = tm.getTransaction(); 

    doSomeThing(); 

    tm.suspend();//挂起当前事务 

    try{ 

      tm.begin();//重新开启第二个事务 

      Transaction ts2 = tm.getTransaction(); 

      methodB(); 

      ts2.commit();//提交第二个事务 

   } 

  Catch(RunTimeException ex){ 

      ts2.rollback();//回滚第二个事务 

  } 

  finally{ 

     //释放资源 

   } 

    //methodB执行完后,复恢第一个事务 

    tm.resume(ts1); 

doSomeThingB(); 

    ts1.commit();//提交第一个事务 

catch(RunTimeException ex){ 

   ts1.rollback();//回滚第一个事务 

finally{ 

   //释放资源 

}


在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。


(5)PROPAGATION_NOT_SUPPORTED  总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。(代码示例同上,可同理推出)


(6)PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常;


(7)PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而nestedTransactionAllowed属性值默认为false;


Java代码:


//事务属性 PROPAGATION_REQUIRED 

methodA(){ 

   doSomeThingA(); 

   methodB(); 

   doSomeThingB(); 

}

 

//事务属性 PROPAGATION_NESTED 

methodB(){ 

  …… 

}


如果单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,相当于下面的效果:


Java代码:


main(){ 

Connection con = null; 

Savepoint savepoint = null; 

try{ 

   con = getConnection(); 

   con.setAutoCommit(false); 

   doSomeThingA(); 

   savepoint = con2.setSavepoint(); 

   try{ 

       methodB(); 

   }catch(RuntimeException ex){ 

      con.rollback(savepoint); 

   } 

   finally{ 

     //释放资源 

  }

 

   doSomeThingB(); 

   con.commit(); 

catch(RuntimeException ex){ 

  con.rollback(); 

finally{ 

   //释放资源 

}


当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。


嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。


PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。


使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不同的支持方式。


PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。


另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务,  它是已经存在事务的一个真正的子事务. 潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。


由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.

PROPAGATION_REQUIRED应该是我们首先的事务传播行为。它能够满足我们大多数的事务需求。

0 0
原创粉丝点击