Friday, November 19, 2021

SpringBoot2异常处理回滚事务详解(自动回滚/手动回滚/部分回滚)

 1 问题背景

有时候,我们总是需要再SpringBoot2中对一个Service方法做一个完整的事务,发现异常时,进行回滚,然后又能返回错误信息。


事务定义

事务,就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。


事务特点

1.原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做


2.一致性:数据不会因为事务的执行而遭到破坏


3.隔离性:一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰


4.持久性:一个事物一旦提交,它对数据库的改变就是永久的。


2 @Transactional 事务实现机制

      Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。


     (1)编程式事务指的是通过编码方式实现事务;


     (2)声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。


       声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。


      声明式事务有两种方式:


       (1)一种是在配置文件(xml)中做相关的事务规则声明,


       (2)另一种是基于@Transactional 注解的方式。


     注释配置是目前流行的使用方式,因此本文将着重介绍基于@Transactional 注解的事务管理。


     在应用系统调用声明了 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional 的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。


      Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。

 




开启事务

方式1:在 xml 配置中的事务配置信息


<tx:annotation-driven />

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource" />

</bean>


方式2:使用@EnableTransactionManagement 注解也可以启用事务管理功能


 


3 场景

3.1 场景一:自动回滚(直接抛出,不try/catch)

@Override

@Transactional(rollbackFor = Exception.class)

public Object submitOrder() throws Exception {  

     success();  

     //假如exception这个操作数据库的方法会抛出异常,方法success()对数据库的操作会回滚。 

     exception(); 

     return ApiReturnUtil.success();

}



3.2 场景二:手动回滚(进行try/catch,回滚并抛出)

@Override

@Transactional(rollbackFor = Exception.class)

public Object submitOrder (){  

    success();  

    try {  

        exception(); 

     } catch (Exception e) {  

        e.printStackTrace();     

        //手工回滚异常

        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        return ApiReturnUtil.error();

     }  

    return ApiReturnUtil.success();

}



3.3 补充:回滚部分异常

使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 设置回滚点。

使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滚到savePoint。



@Override

@Transactional(rollbackFor = Exception.class)

public Object submitOrder (){  

    success();  

    //只回滚以下异常,

    Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();

    try {  

        exception(); 

     } catch (Exception e) {  

        e.printStackTrace();     

        //手工回滚异常

        TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);

        return ApiReturnUtil.error();

     }  

    return ApiReturnUtil.success();

}

 


3.4 使用DataSourceTransactionManager

springboot 开启事务以及手动提交事务,可以在服务类上加上两个注解


@Autowired

DataSourceTransactionManager dataSourceTransactionManager;

@Autowired

TransactionDefinition transactionDefinition;

手动开启事务

TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

手动提交事务

dataSourceTransactionManager.commit(transactionStatus);//提交

手动回滚事务

dataSourceTransactionManager.rollback(transactionStatus);//最好是放在catch 里面,防止程序异常而事务一直卡在哪里未提交



4 spring boot controller设置 @Transactional 不回滚的解决办法

在spring boot 中,使用事务非常简单,直接在方法上面加入@Transactional 就可以实现,以下是我的做法



    @GetMapping("delete")

    @ResponseBody

    @Transactional    

    public void delete(@RequestParam("id") int id) {       

     try {          


            //delete country

            this.repository.delete(id);         

            if(id == 1){              

                throw Exception("测试事务");

            }           

             //delete city

            this.repository.deleteByCountryId(id);

        }catch (Exception e){

            logger.error("delete false:" + e.getMessage());        

            return new MessageBean(101,"delete false");

        }

    }



发现事务不回滚,即 this.repository.delete(id); 成功把数据删除了。


原因:


      默认spring事务只在发生未被捕获的 RuntimeException 时才回滚。  

      spring aop  异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获 RuntimeException 的异常,但可以通过配置来捕获特定的异常并回滚。

      换句话说在service的方法中不使用try catch 或者在catch中最后加上throw new runtimeexcetpion(),这样程序异常时才能被aop捕获进而回滚。


解决方案: 

  方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理

  方案2:在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常



@GetMapping("delete") 

@ResponseBody 

@Transactional 

public Object delete(@RequestParam("id") int id){ 

    if (id < 1){

         return new MessageBean(101,"parameter wrong: id = " + id) ; 

     } 

     try { 

         //delete country

          this.countryRepository.delete(id);

          //delete city

          this.cityRepository.deleteByCountryId(id);

          return new MessageBean(200,"delete success");

      }catch (Exception e){

          logger.error("delete false:" + e.getMessage());

          TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

          return new MessageBean(101,"delete false");

      }

}


5 避免 Spring 的 AOP 的自调用问题

    在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。见清单 5 举例代码展示。


清单 5.自调用问题举例


@Service

public class OrderService {

    private void insert() {

        insertOrder();

    }


    @Transactional

    public void insertOrder() {

        //insert log info

        //insertOrder

        //updateAccount

    }

}


insertOrder 尽管有@Transactional 注解,但它被内部方法 insert()调用,事务被忽略,出现异常事务不会发生回滚。


这样一个错org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope

比如例子:


下面我们来看两种写法,第一种


@Transactional

    public UserEntity login1(UserEntity user) {

        userDao.update(6);

        if(userDao.update(6)){

            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        }

        return user;

    }

第二种


    public UserEntity login(UserEntity user) {

        this.test();

        return user;

    }

    

    @Transactional

    public void test(){

        userDao.update(6);

        if(userDao.update(6)){

            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        }

    }

      第一种写法的时候,回滚是起作用的.


      第二种写法的时候就会报错,错误就是一开始提到的: org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope


为什么会这样呢?


      此错在没有Transaction无法回滚事务。自调用导致@Transactional 失效。


      spring里事务是用注解配置的,当一个方法没有接口,单单只是一个方法不是服务时,事务的注解是不起作用的,需要回滚时就会报错。


      出现这个问题的根本原因在于AOP的实现原理。由于@Transactional 的实现原理是AOP,AOP的实现原理是动态代理,换句话说,自调用时不存在代理对象的调用,这时不会产生我们注解@Transactional 配置的参数,自然无效了。


      虽然可以直接从容器中获取代理对象,但这样有侵入之嫌,不推荐。


也是在此记录一笔:


      事务必须用在服务上,且一个服务一个事务,不得嵌套。

 


      sping的事务是通过注解配置上去的,而下面的那个方法并没有接口,在实现类里面只是一个简单的方法而已,对于事务的注解来说没有任何作用,所以在这个方法里面调用回滚的方法自然就报错了。


      所以在以后的项目中如果你要使用事务,那么请记住,一个服务一个事务,一次请求一个事务,千万不要想着用调用方法,然后再一个方法上面加事务。你只能调用另外一个服务,在另外一个服务上面加事务。


解决方法:

1、可以在类服务上加事务:


@Transactional

public class MyTransactional {

    public UserEntity login(UserEntity user) {

        this.test();

        return user;

    }

    

    //@Transactional  服务上加了@Transactional后  这里可以不加

    public void test(){

        userDao.update(6);

        if(userDao.update(6)){

            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        }

    }

}

 2、使用AspectJ 取代 Spring AOP 代理


    上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。


需要将下面的 AspectJ 信息添加到 xml 配置信息中。


清单 6. AspectJ 的 xml 配置信息


<tx:annotation-driven mode="aspectj" />

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource" />

</bean>

</bean

class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"

factory-method="aspectOf">

<property name="transactionManager" ref="transactionManager" />

</bean>


 


同时在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。


清单 7. AspectJ 的 pom 配置信息


<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-aspects</artifactId>

    <version>4.3.2.RELEASE</version>

    </dependency>

<dependency>

    <groupId>org.aspectj</groupId>

    <artifactId>aspectjrt</artifactId>

    <version>1.8.9</version>

</dependency>


<plugin>

<groupId>org.codehaus.mojo</groupId>

<artifactId>aspectj-maven-plugin</artifactId>

<version>1.9</version>

<configuration>

    <showWeaveInfo>true</showWeaveInfo>

    <aspectLibraries>

    <aspectLibrary>

        <groupId>org.springframework</groupId>

        <artifactId>spring-aspects</artifactId>

        </aspectLibrary>

    </aspectLibraries>

</configuration>


<executions>

<execution>

    <goals>

        <goal>compile</goal>

        <goal>test-compile</goal>

    </goals>

</execution>

</executions>

</plugin>


 


5 详细说明

5.1 事务管理方式

在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式。


编程式事务管理: 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单,推荐使用。

5.2 事务处理 @Transactional注解的使用

Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

@Transactional 注解应该只被应用到 public 修饰的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。也就是默认对RuntimeException()异常或是其子类进行事务回滚。checked异常,即Exception可try{}捕获的不会回滚,因此对于我们自定义异常,通过rollbackFor进行设定。

如果我们需要捕获异常后,同时进行回滚,通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();进行手动回滚操作。

使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 设置回滚点,使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滚到savePoint。

5.3 事务提交方式

       默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。


       对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。不过,这个我们不用担心,spring会将底层连接的自动提交特性设置为false。也就是在使用spring进行事物管理的时候,spring会将是否自动提交设置为false,等价于JDBC中的 connection.setAutoCommit(false);,在执行完之后在进行提交,connection.commit(); 。


5.4 注解@Transactional常用配置

参 数 名 称 功 能 描 述

readOnly 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)

rollbackFor    rollbackFor    该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

rollbackForClassName 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”})

noRollbackFor 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

noRollbackForClassName 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”})

propagation 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

isolation 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置

timeout 该属性用于设置事务的超时秒数,默认值为-1表示永不超时

事物超时设置: @Transactional(timeout=30) //默认是30秒


5.5 Propagation的属性(事务的传播行为)

例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)


Propagation属性


含义


REQUIRED


默认值 在有transaction状态下执行;如当前没有transaction,则创建新的transaction;


SUPPORTS


如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执行;


MANDATORY


必须在有transaction状态下执行,如果当前没有transaction,则抛出异常IllegalTransactionStateException;


REQUIRES_NEW


创建新的transaction并执行;如果当前已有transaction,则将当前transaction挂起;


NOT_SUPPORTED


在无transaction状态下执行;如果当前已有transaction,则将当前transaction挂起;


NEVER


在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException。


5.6 事务5种隔离级别

例如:@Transactional(isolation = Isolation.READ_COMMITTED)


隔离级别 含义

DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.

另外四个与JDBC的隔离级别相对应;

READ_UNCOMMITTED 最低的隔离级别。事实上我们不应该称其为隔离级别,因为在事务完成前,其他事务可以看到该事务所修改的数据。而在其他事务提交前,该事务也可以看到其他事务所做的修改。可能导致脏,幻,不可重复读

READ_COMMITTED 大多数数据库的默认级别。在事务完成前,其他事务无法看到该事务所修改的数据。遗憾的是,在该事务提交后,你就可以查看其他事务插入或更新的数据。这意味着在事务的不同点上,如果其他事务修改了数据,你就会看到不同的数据。可防止脏读,但幻读和不可重复读仍可以发生。

REPEATABLE_READ 比ISOLATION_READ_COMMITTED更严格,该隔离级别确保如果在事务中查询了某个数据集,你至少还能再次查询到相同的数据集,即使其他事务修改了所查询的数据。然而如果其他事务插入了新数据,你就可以查询到该新插入的数据。可防止脏读,不可重复读,但幻读仍可能发生。

SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。代价最大、可靠性最高的隔离级别,所有的事务都是按顺序一个接一个地执行。避免所有不安全读取。

 


5.7 事务回滚规则

指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。


默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。


可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。


5.8 事物注意事项

要根据实际的需求来决定是否要使用事物,最好是在编码之前就考虑好,不然到以后就难以维护;

如果使用了事物,请务必进行事物测试,因为很多情况下以为事物是生效的,但是实际上可能未生效!

事物@Transactional的使用要放再类的公共(public)方法中,需要注意的是在 protected、private 方法上使用 @Transactional 注解,它也不会报错(IDEA会有提示),但事务无效。

事物@Transactional是不会对该方法里面的子方法生效!也就是你在公共方法A声明的事物@Transactional,但是在A方法中有个子方法B和C,其中方法B进行了数据操作,但是该异常被B自己处理了,这样的话事物是不会生效的!反之B方法声明的事物@Transactional,但是公共方法A却未声明事物的话,也是不会生效的!如果想事物生效,需要将子方法的事务控制交给调用的方法,在子方法中使用rollbackFor注解指定需要回滚的异常或者将异常抛出交给调用的方法处理。一句话就是在使用事物的时候子方法最好将异常抛出!

事物@Transactional由spring控制的时候,它会在抛出异常的时候进行回滚。如果自己使用catch捕获了处理了,是不生效的,如果想生效可以进行手动回滚或者在catch里面将异常抛出,比如throw new RuntimeException();。

@Transactional可以放在Controller下面直接起作用,看到网上好多同学说要放到@Component下面或者@Service下面,经过试验,可以不用放在这两个下面也起作用。


@Transactional引入包问题,她有两个包:import javax.transaction.Transactional; 和 import org.springframework.transaction.annotation.Transactional; 这两个都可以用,对比了一下他们两个的方法和属性,发现后面的比前面的强大。建议后后面的。


PlatformTransactionManager 这个接口中定义了三个方法 getTransaction创建事务,commit 提交事务,rollback 回滚事务。她的实现类是 AbstractPlatformTransactionManager这个。


5.9 事务并发会产生什么问题

术语 含义

脏读 A事务读取到了B事务还未提交的数据,如果B未提交的事务回滚了,那么A事务读取的数据就是无效的,这就是数据脏读

不可重复读 在同一个事务中,多次读取同一数据返回的结果不一致,这是由于读取事务在进行操作的过程中,如果出现更新事务,它必须等待更新事务执行成功提交完成后才能继续读取数据,这就导致读取事务在前后读取的数据不一致的状况出现

幻读 A事务读取了几行记录后,B事务插入了新数据,并且提交了插入操作,在后续操作中A事务就会多出几行原本不存在的数据,就像A事务出现幻觉,这就是幻读

 


1)第一类丢失更新:

在没有事务隔离的情况下,两个事务都同时更新一行数据,但是第二个事务却中途失败退出, 导致对数据的两个修改都失效了。


例如:


张三的工资为5000,事务A中获取工资为5000,事务B获取工资为5000,汇入100,并提交数据库,工资变为5100,


随后


事务A发生异常,回滚了,恢复张三的工资为5000,这样就导致事务B的更新丢失了。


2)脏读:

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

例如:

  张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。

  与此同时,

  事务B正在读取张三的工资,读取到张三的工资为8000。

  随后,

  事务A发生异常,而回滚了事务。张三的工资又回滚为5000。

  最后,

  事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。


3)不可重复读:

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

例如:

  在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。

  与此同时,

  事务B把张三的工资改为8000,并提交了事务。

  随后,

  在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。


4)第二类丢失更新:

不可重复读的特例。

有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。


例如:


在事务A中,读取到张三的存款为5000,操作没有完成,事务还没提交。

  与此同时,

  事务B,存储1000,把张三的存款改为6000,并提交了事务。

  随后,

  在事务A中,存储500,把张三的存款改为5500,并提交了事务,这样事务A的更新覆盖了事务B的更新。


5)幻读:

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

例如:

  目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。

  此时,

  事务B插入一条工资也为5000的记录。

  这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。


提醒:

    不可重复读的重点是修改,同样的条件,你读取过的数据,再次读取出来发现值不一样了

    幻读的重点在于新增或者删除,同样的条件,第 1 次和第 2 次读出来的记录数不一样

 


6 结语

   由于本人才接触SprintBoot,文中如有没说清楚的地方,希望大家能在评论区指出,以帮助我将博文写得更好。

————————————————

版权声明:本文为CSDN博主「zzhongcy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zzhongcy/article/details/102893309

Saturday, December 28, 2013

ServiceMix Process Examples

1.HTTP-binding(ServiceMix)


1.4 各组件间传递消息流的过程如下:
1.        HttpClientHttp客户端运行一个独立的Java客户端程序,通过其中的URLConnection类连接到http://localhost:8912,将request.xml文件发送到此端口。
2.        HttpReceiver:在Http服务器上(Http server)HttpReceiver通过监听http://localhost:8912端口,得到此消息。
3.        HttpReceiver根据消息的destinationService(目标服务)属性中的信息,将此消息通过NMR发送给相应的stockQuote服务。
4.        stockQuote再将此消息发送给另一个服务——soapEndpoint来处理。
5.        soapEndpoint处理之后,将响应消息返回给stockQuote
6.        stockQuote通过NMR将此相应消息发送给httpReceiver
7.        httpReceiver将相应消息发送到http://localhost:8912端口。
8.        HttpClient从端口读取该响应消息。
9.        最终响应消息在控制台输出。

2.Jms-binding (ServiceMix)


2.4消息流按以下方式通过各个组件:
  1. JMSClient, 通过 ActiveMQConnectionFactory, 连接到一个叫"demo.org.servicemix.source"的主题 并发送一个文本消息。
  2. jencks (JCA资源适配器在端口61616上监听消息
  3. inputReceiver 通过jencks订阅"demo.org.servicemix.source" 主题并接收JMS消息
  4. inputReceiver规格化JMS消息并通过规格化消息路由器(NMR)把它发送到 outputSender
  5. outputSender outputSender把规格化的消息marshal为一个JMS消息,并使用jmsTemplate"demo.org.servicemix.result" 主题上发布消息。
  6. jmsTemplate在"demo.org.servicemix.result" 主题上发布消息,使用jmsFactory获得一个到结果主题的连接。
  7. JMSClient, "demo.org.servicemix.result," 主题的另一个订阅者接收这个消息。
  8. 响应在控制台上打印。
 3.BPEL Example(ServiceMix)



3.5程序的逻辑流:
1.        JMSClient,通过ActiveMQConnectionFactory连接到一个叫"demo.org.servicemix.source"的主题上,并发送一个包含message.soap file.的文本消息。
2.        myComponent, "demo.org.servicemix.source,"主题的一个订阅者,接收这个消息。
3.        myComponent 实现类,JmsServiceComponent,通过ServiceMix总线发送消息至PxeBpelEngine,执行它的onMessage()方法。destinationService属性定了了消息的目的地。注意:destinationService属性在文件servicemix.xml中。
4.        PxeBpelEngine通过ServiceMix总线、NMR发送一个响应至myComponent 
5.      myComponent 使用 jmsTemplate bean 发布消息。.
6.        jmsTemplate 使用 jmsFactory bean获得一个到和JMS 主题"demo.org.servicemix.source."相关的端口的连接。这个消息被发布在"demo.org.servicemix.source" 主题.
7.      JMSClient, "demo.org.servicemix.source,"主题的订阅者, 接收这个消息。
8.        响应在控制台上打印。

4.RSS-binding(ServiceMix)



5.File binding(ServiceMix)  


5.4程序的逻辑流:
1.        FilePoller 1000ms检索收件箱目录一次寻找一个文件
2.        一旦在收件箱目录中找到一个文件,filePollerworkManager处获得一个线程。这个线程用来处理这个文件。
3.        filePoller创建一个包含了将要被传输的文件的规格化的消息,它把这个规格化的消息发送到NMRNMR把这个消息路由到fileSender组件。
4.        fileSender把规格化的消息转换到一个文件并“发送”(放置)到发件箱目录。
文件传输过程中,日志信息被写到控制台。

6.Basic(ServiceMix)



6.4  消息流按以下方式通过各个组件:
1.        Timer组件通过规格化消息路由(NMR)向inputSender发送一个消息。
2.        inputsender把消息转换(marshals)为一个JMS消息,然后用jmsTemplate bean发布消息。
3.        jmsTemplatejmsFactorybean获得一个到和JMS 主题"demo.org.servicemix.source."相关的端口的连接。这个消息被发布在"demo.org.servicemix.source" 主题.
4.        jencks (the JCA资源适配器)在端口61616上监听消息
5.        inputReceiver通过jencks订阅"demo.org.servicemix.source" 主题并接收JMS消息
6.        inputReceiver规格化JMS消息并通过NMR把它发送到outputSender
7.        outputSender把规格化的消息marshal为一个JMS消息,并使用jmsTemplate"demo.org.servicemix.result" 主题上发布消息。
8.        jmsTemplate"demo.org.servicemix.result" 主题上发布消息,使用jmsFactory获得一个到结果主题的连接。
9.        Jencks61616端口上监听消息。
10.    jmsTrace订阅"demo.org.servicemix.result" 主题,并通过jencks接收JMS消息。
11.    jmsTraceJMS消息转换成规格化消息,并通过NMR把它发送到trace
12.    trace把规格化的消息转换成一个字符串,并在控制台上记录日志。

5秒在控制台上写一遍日志,还有从trace组件而来的信息。注意,他日ggers&apos,属性值为“名字”“我的示例工作”“组”,“ServiceMix”用时间戳显示。

7.Quartz-binding(ServiceMix) 


This article is from :

http://blog.csdn.net/juset/archive/2006/12/18/1447626.aspx

Thursday, October 10, 2013

Set the context path of a web application in Tomcat 7.0

Try to deploy a war package in Tomcat 7 with different web context path, at last it works with below steps:

1. Create context.xml file with your new context path name. For example:  mynewpath.xml

Don't just use default file name context.xml as your web file name, otherwise the Tomcat will deploy a new app with \context . But you want \mynewpath . It is a tricky.

In file you need to add:

<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="myapp.war"
  reloadable="true" path="/mynewpath"/>

2. Put this mynewpath.xml in Tomcat\conf\Catalina\localhost.
3. Deploy your war file outside the webapp folder like below:

C:\apache-tomcat-7.0.42\myapp.war


That's it.


Monday, April 30, 2012

Run JSF 2.0 on Glassfish 2.1.1

You can add JSF 2.0 and Unified EL (EL 2) magic to Glassfish V2.1.1 in 2 ways.

1) If you want to completly replace JSF 1.x with 2.x in your glassfish V2.1.1 installation


a) Remove existing jsf-impl.jar in ${com.sun.aas.installRoot}/lib
b) Add following JSF2 and unified EL jars. i.e. jsf-api-2.1.jar, jsf-impl-2.1.jar,el-api-2.1.jar, el-impl-2.1.jar in in ${com.sun.aas.installRoot}/lib.
c) Add or update classpath-prefix=”${com.sun.aas.installRoot}/lib/jsf-api.jar;${com.sun.aas.installRoot}/lib/jsf-imp.jar;${com.sun.aas.installRoot}/lib/el-api.jar;${com.sun.aas.installRoot}/lib/el-impl.jar” in ${com.sun.aas.installRoot}/domains//config/domain.xml under as an attribute.


2) If you want to keep JSF 1.x as the default implementation on your glassfish instance but want to implement JSF 2.x only on one of your applications


a) Add el-api-2.1.jar, el-impl-2.1.jar in ${com.sun.aas.installRoot}/lib.
b) Include jsf-api-2.1.jar, jsf-impl-2.1.jar in lib directory of your application war.
c) Add add sun-web.xml under /WEB-INF/ directory of your application war with following properties
mojarra2
d) Add or update classpath-prefix=”${com.sun.aas.installRoot}/lib/el-api.jar;${com.sun.aas.installRoot}/lib/el-impl.jar” in ${com.sun.aas.installRoot}/domains//config/domain.xml under as an attribute.
Hope this helps. Enjoy!!!
Note: (This does not work in Glassfish V2.0 or V2.1)


Original file:
http://ocpsoft.org/java/jsf2-java/jsf2-how-to-add-the-magic-of-el-el2-to-jsf/

Also there is another article show the way:

Saturday, April 28, 2012

Spring+Hibernate+JSF+Ajax4jsf+Acegi+jCaptcha 做登录模块

带图片验证码的用户登录是一个系统中很常见的模块,在Java EE中有很多不同的实现,而开源世界中又给我们提供了很多优秀的框架,从而减少了我们的编码量,下面给大家简单介绍一下用 Spring+Hibernate+JSF+Ajax4jsf+Acegi+jCaptcha 如何构建一个登录模块:
    我使用这些框架的版本如下:
    Spring 2.0.1
    Hibernate 3.2
    JSF 1.2
    Acegi 1.0.5
    jCaptcha 1.0-RC6
    首先建表USERTEST,和ROLETEST,作为用户表和角色表
CREATE TABLE USERTEST
(
  USERID    NUMBER                              NOT NULL,
  USERNAME  VARCHAR2(20 CHAR)                   NOT NULL,
  PASSWORD  VARCHAR2(100 CHAR)                  NOT NULL,
  STATUS    NUMBER                              DEFAULT 1                     NOT NULL,
  ROLEID    NUMBER                              NOT NULL
)

CREATE TABLE ROLETEST
(
  ROLEID    NUMBER                              NOT NULL,
  ROLENAME  VARCHAR2(50 CHAR)                   NOT NULL,
  ROLEDESC  VARCHAR2(50 CHAR)
)

    同时给ROLETEST表添加一条记录:1    ROLE_USER    普通用户
    再给USERTEST表添加一条记录:1    admin    123    1    1
    对应的hbm.xml文件就相信大家都会写,如果用eclipse开发的话,可以使用HibernateSynchronizer插件,我使用的版本是3.1.9
    接下来就要给web.xml的配置一下了:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 id="WebApp_ID" version="2.5">
 <display-name>Demo</display-name>
 <welcome-file-list>
  <welcome-file>index.html</welcome-file>
  <welcome-file>index.htm</welcome-file>
  <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
   /WEB-INF/classes/applicationContext.xml
  </param-value>
 </context-param>
 <context-param>
  <param-name>javax.faces.CONFIG_FILES</param-name>
  <param-value>/WEB-INF/faces-config.xml</param-value>
 </context-param>
 <listener>
  <listener-class>
   org.springframework.web.context.ContextLoaderListener
  </listener-class>
 </listener>
 <listener>
  <listener-class>
   org.acegisecurity.ui.session.HttpSessionEventPublisher
  </listener-class>
 </listener>
 <listener>
  <listener-class>
   com.sun.faces.config.ConfigureListener
  </listener-class>
 </listener>
 <filter>
  <filter-name>OpenSessionInView</filter-name>
  <filter-class>
   org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
  </filter-class>
  <init-param>
   <param-name>singleSession</param-name>
   <param-value>true</param-value>
  </init-param>
 </filter>
 <filter-mapping>
  <filter-name>OpenSessionInView</filter-name>
  <url-pattern>*.jsf</url-pattern>
 </filter-mapping>
 <filter>
  <filter-name>Acegi Filter Chain Proxy</filter-name>
  <filter-class>
   org.acegisecurity.util.FilterToBeanProxy
  </filter-class>
  <init-param>
   <param-name>targetClass</param-name>
   <param-value>
    org.acegisecurity.util.FilterChainProxy
   </param-value>
  </init-param>
 </filter>
 <filter-mapping>
  <filter-name>Acegi Filter Chain Proxy</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>REQUEST</dispatcher>
 </filter-mapping>
 <filter>
  <display-name>Ajax4jsf Filter</display-name>
  <filter-name>ajax4jsf</filter-name>
  <filter-class>org.ajax4jsf.Filter</filter-class>
  </filter>
 
  <filter-mapping>
  <filter-name>ajax4jsf</filter-name>
  <servlet-name>Faces Servlet</servlet-name>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>INCLUDE</dispatcher>
  </filter-mapping>


 <servlet>
  <servlet-name>Faces Servlet</servlet-name>
  <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>Faces Servlet</servlet-name>
  <url-pattern>*.jsf</url-pattern>
 </servlet-mapping>
 </web-app>

    然后Spring的applicationContext.xml,在这里面你需要配置数据库连接、hibernate的各种策略、Acegi安全设置、jCaptcha设置等等:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.1.xsd">
 <bean id="dataSource"
  class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName">
   <value>oracle.jdbc.driver.OracleDriver</value>
  </property>
  <property name="url">
   <value>jdbc:oracle:thin:@10.10.10.10:1521:demo</value>
  </property>
  <property name="username">
   <value>demo</value>
  </property>
  <property name="password">
   <value>demo</value>
  </property>
 </bean>

 <bean id="sessionFactory"
  class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="dataSource">
   <ref bean="dataSource" />
  </property>
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.dialect">
     org.hibernate.dialect.OracleDialect
    </prop>
    <prop key="hibernate.connection.pool_size">5</prop>
    <prop key="hibernate.show_sql">true</prop>
    <prop key="cache.provider_class">
     org.hibernate.cache.OSCacheProvider
    </prop>
    <prop key="cache.use_query_cache">true</prop>
    <prop key="cglib.use_reflection_optimizer">false</prop>
   </props>
  </property>
  <property name="mappingDirectoryLocations">
   <list><!-- 这里指定了hibernate的映射目录 -->
    <value>classpath:/com/fifthlab/demo/pojo</value>
   </list>
  </property>
 </bean>

 <bean id="transactionManager"
  class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
 </bean>

 <!-- Transaction template for Managers -->
 <bean id="txProxyTemplate" abstract="true"
  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager" ref="transactionManager" />
  <property name="transactionAttributes">
   <props>
    <prop key="save*">PROPAGATION_REQUIRED</prop>
    <prop key="add*">PROPAGATION_REQUIRED</prop>
    <prop key="delete*">PROPAGATION_REQUIRED</prop>
    <prop key="update*">PROPAGATION_REQUIRED</prop>
    <prop key="relation*">PROPAGATION_REQUIRED</prop>
    <prop key="disconnect*">PROPAGATION_REQUIRED</prop>
    <prop key="assign*">PROPAGATION_REQUIRED</prop>
    <prop key="unassign*">PROPAGATION_REQUIRED</prop>
    <prop key="*">PROPAGATION_SUPPORTS</prop>
   </props>
  </property>
 </bean>

<!-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX acegi part begin XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -->
 <!-- ======================== FILTER CHAIN ======================= -->
 <!--  if you wish to use channel security, add "channelProcessingFilter," in front
  of "httpSessionContextIntegrationFilter" in the list below -->
 <bean id="filterChainProxy"
  class="org.acegisecurity.util.FilterChainProxy">
  <property name="filterInvocationDefinitionSource">
   <value>
    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
    PATTERN_TYPE_APACHE_ANT
    /**/*.jsf=httpSessionContextIntegrationFilter,captchaValidationProcessingFilter,authenticationProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
   </value>
  </property>
 </bean>

 <bean id="httpSessionContextIntegrationFilter"
  class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
  <property name="context">
   <value><!-- 此处修复了Acegi和jCaptcha不能兼容的一个bug,在后面的版本中应该修复了 -->
    com.fifthlab.demo.dao.authentication.FixedCaptchaSecurityContextImpl
   </value>
  </property>
 </bean>

 <bean id="authenticationManager"
  class="org.acegisecurity.providers.ProviderManager">
  <property name="providers">
   <list>
    <ref local="daoAuthenticationProvider" />
   </list>
  </property>
 </bean>

 <bean id="daoAuthenticationProvider"
  class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
  <property name="userDetailsService">
   <ref local="hibernate3Authentication" />
  </property>
 </bean>

 <bean id="loggerListener"
  class="org.acegisecurity.event.authentication.LoggerListener" />

 <bean id="authenticationProcessingFilter"
  class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
  <property name="authenticationManager">
   <ref bean="authenticationManager" />
  </property>
  <property name="authenticationFailureUrl">
   <value>/login.jsf</value>
  </property>
  <property name="defaultTargetUrl">
   <value>/login.jsf</value>
  </property>
  <property name="filterProcessesUrl">
   <value>/j_acegi_security_check</value>
  </property>
 </bean>

 <bean id="exceptionTranslationFilter"
  class="org.acegisecurity.ui.ExceptionTranslationFilter">
  <property name="authenticationEntryPoint">
   <ref local="authenticationProcessingFilterEntryPoint" />
  </property>
 </bean>

 <bean id="authenticationProcessingFilterEntryPoint"
  class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
  <property name="loginFormUrl">
   <value>/login.jsf</value>
  </property>
  <property name="forceHttps">
   <value>false</value>
  </property>
 </bean>

 <bean id="filterInvocationInterceptor"
  class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
  <property name="authenticationManager">
   <ref bean="authenticationManager" />
  </property>
  <property name="accessDecisionManager">
   <ref local="httpRequestAccessDecisionManager" />
  </property>
  <property name="observeOncePerRequest">
   <value>true</value>
  </property>
  <property name="objectDefinitionSource">
   <value>
    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
    PATTERN_TYPE_APACHE_ANT
    /login.jsp=ROLE_ANONYMOUS
    /success.jsp=ROLE_USER
   </value>
  </property>
 </bean>

 <!-- 用来验证用户名密码是否相符,并赋予相应的角色 -->
 <bean id="hibernate3Authentication"
  class="com.fifthlab.demo.dao.authentication.Hibernate3Authentication">
  <property name="sessionFactory">
   <ref local="sessionFactory" />
  </property>
 </bean>

 <bean id="anonymousProcessingFilter"
  class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
  <property name="key">
   <value>foobar</value>
  </property>
  <property name="userAttribute">
   <value>anonymousUser,ROLE_ANONYMOUS</value>
  </property>
 </bean>

 <bean id="httpRequestAccessDecisionManager"
  class="org.acegisecurity.vote.AffirmativeBased">
  <property name="allowIfAllAbstainDecisions">
   <value>false</value>
  </property>
  <property name="decisionVoters">
   <list>
    <ref bean="roleVoter" />
   </list>
  </property>
 </bean>

 <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter" />
<!-- 下面开始添加 jcaptcha  -->
<bean id="captchaValidationProcessingFilter"
  class="org.acegisecurity.captcha.CaptchaValidationProcessingFilter">
  <property name="captchaService">
   <ref bean="captchaService" />
  </property>
  <property name="captchaValidationParameter">
   <value>j_captcha_response</value>
  </property>
 </bean>

 <!-- 用来验证图片验证码是否输入正确 --><bean id="captchaService"
  class="com.fifthlab.demo.dao.authentication.JCaptchaServiceProxyImpl">
  <property name="jcaptchaService" ref="jcaptchaService" />
 </bean>
 <bean id="jcaptchaService"
  class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService" />
 <!-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX acegi part end XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -->

</beans>
    先解释一下上面applicationContext.xml中红色的部分是jCaptcha的一个filter,必须放在第二个位置,后面的是 acegi的filter,也就是说,要先验证图片验证码是否正确,再让acegi来验证用户名和密码。蓝色的部分是需要写的几个代码,其中有一个修复 bug的地方,如果不添加那个bean,在运行的时候就会报Cast转型的错误。下面列一下蓝色部分的代码:
FixedCaptchaSecurityContextImpl:
import org.acegisecurity.captcha.CaptchaSecurityContextImpl;
public class FixedCaptchaSecurityContextImpl extends CaptchaSecurityContextImpl {
 public int hashCode() {

  if (getAuthentication() == null) {
   return (int) System.currentTimeMillis();
  } else {
   return this.getAuthentication().hashCode();
  }
 }

}
Hibernate3Authentication:
import java.util.ArrayList;
import java.util.List;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.hibernate.Criteria;
import org.hibernate.criterion.Expression;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.fifthlab.demo.pojo.Roletest;
import com.fifthlab.demo.pojo.Usertest;
public class Hibernate3Authentication extends HibernateDaoSupport implements UserDetailsService {
 public UserDetails loadUserByUsername(final String username)
 {
  Criteria criteria = getHibernateTemplate().getSessionFactory().getCurrentSession().createCriteria(Usertest.class);
  criteria.add(Expression.eq(Usertest.PROP_USERNAME, username));
  List users = criteria.list();
  if (users.size() == 0)
  {
   throw new UsernameNotFoundException("User not found");
  }
  Usertest user = (Usertest) users.get(0);
  // get User roles of this user
  List<Roletest> userRoles = new ArrayList<Roletest>();
  userRoles.add(user.getRoletest());
  GrantedAuthority[] dbAuths = new GrantedAuthority[userRoles.size()];
  if (userRoles.size() == 0)
  {
   throw new UsernameNotFoundException("User has no GrantedAuthority");
  }
  // grant authority to user
  String roleName = null;
  Roletest role = null;
  for (int i = 0; i < userRoles.size(); i++)
  {
   role = (Roletest) userRoles.get(i);
   if (null != role)
   {
    roleName = role.getRolename();
    dbAuths[i] = new GrantedAuthorityImpl(roleName); 
   }
  }

  user.setAuthorities(dbAuths);

  return user;
 }
}
JCaptchaServiceProxyImpl:
import org.acegisecurity.captcha.CaptchaServiceProxy;
import com.octo.captcha.service.image.ImageCaptchaService;
import com.octo.captcha.service.CaptchaServiceException;
public class JCaptchaServiceProxyImpl implements CaptchaServiceProxy {
   
    private ImageCaptchaService jcaptchaService;
 
    public boolean validateReponseForId(String id, Object response) {
 
      try {
       boolean result = jcaptchaService.validateResponseForID(id, response);
       //System.out.println("id:"+id+"\nresponse:"+response+"\nresult:"+result);
       return result;
 
      } catch (CaptchaServiceException cse) {
        //fixes known bug in JCaptcha
       cse.printStackTrace();
        return false;
      }
    }
 
    public void setJcaptchaService(ImageCaptchaService jcaptchaService) {
      this.jcaptchaService = jcaptchaService;
    }
  }
    接下来是简单的登录页面login.jsp和登录成功后的跳转页面success.jsp:
login.jsp:
<%@ page language="Java" contentType="text/html;charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="https://ajax4jsf.dev.java.net/ajax" prefix="a4j"%>
<f:view>
 <html>
 <head>
 <title>hello</title>
 </head>
 <META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
 <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
 <META HTTP-EQUIV="Expires" CONTENT="Expires">
 <body>

 <h:form id="form1">
  <h:panelGrid columns="1">
   <h:panelGroup>
    <h:outputLabel value="username:" for="username" />
    <h:inputText id="username" value="#{loginBean.userName}" />
   </h:panelGroup>
   <h:panelGroup>
    <h:outputLabel value="password:" for="password" />
    <h:inputSecret id="password" value="#{loginBean.password}" />
   </h:panelGroup>
   <h:panelGroup>
    <h:outputLabel value="verifying code:" for="verifyingCode" />
    <h:inputText id="verifyingCode" value="#{loginBean.verifyingCode}" />
    <a4j:mediaOutput element="img" cacheable="false" session="false"
        createContent="#{captchaImageBean.paint}" id="showPic"
        mimeType="image/jpeg" />
    <a4j:commandLink reRender="showPic" requestDelay="40" >
     <h:outputText value="refresh" />
    </a4j:commandLink>
   </h:panelGroup>
   <h:panelGroup>
    <h:commandButton id="commandButton1" value="Submit"
     action="#{loginBean.authenticate}" />
   </h:panelGroup>
  </h:panelGrid>
 </h:form>

 </body>
 </html>
</f:view>

    其中红色部分是应用了Ajax4jsf这个框架的两个标签,<a4j:mediaOutput> 是表示输出一个媒体,这里我选择的是输出图片,它会异步的产生图片,而不必和页面载入时同步取图片。而<a4j:commandLink reRender="showPic"> 则是在看不清图片的情况下通过异步的方式刷新上面那个<a4j:mediaOutput>的图片。
    success.jsp的页面很简单,就是显示一个“Welcome here”的字样,因此不列出来了。
    然后我们看一下页面中用到的backing bean的代码:
LoginBean:
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.fifthlab.demo.dao.authentication.JCaptchaServiceProxyImpl;
public class LoginBean extends HibernateDaoSupport {
 private static final long serialVersionUID = 1L;
 private String userName;
 private String password;
 private String verifyingCode;
 private AuthenticationManager authenticationManager;
 private JCaptchaServiceProxyImpl captchaService;
 /**
  * @return 返回用户输入的验证码
  */
 public String getVerifyingCode()
 {
  return this.verifyingCode;
 }

 /**
  * @param verifyingCode 设置图片验证码
  */
 public void setVerifyingCode(String verifyingCode)
 {
  this.verifyingCode = verifyingCode;
 }

 /**
  * @return 返回captchaService来验证图片验证码
  */
 public JCaptchaServiceProxyImpl getCaptchaService()
 {
  return this.captchaService;
 }

 /**
  * @param captchaService 设置图片验证码服务
  */
 public void setCaptchaService(JCaptchaServiceProxyImpl captchaService)
 {
  this.captchaService = captchaService;
 }

 /**
  * @return 返回密码
  */
 public String getPassword()
 {
  return password;
 }
 /**
  * @param password 设置密码
  *           
  */
 public void setPassword(String password)
 {
  this.password = password;
 }
 /**
  * @return 返回用户名
  */
 public String getUserName()
 {
  return userName;
 }
 /**
  * @param userName 设置用户名
  *           
  */
 public void setUserName(String userName)
 {
  this.userName = userName;
 }
 /**
  * @return 返回验证管理器
  */
 public AuthenticationManager getAuthenticationManager()
 {
  return authenticationManager;
 }
 /**
  * @param authenticationManager 设置验证管理器
  *           
  */
 public void setAuthenticationManager(AuthenticationManager authenticationManager)
 {
  this.authenticationManager = authenticationManager;
 }

 public String authenticate()
 {
  try
  {
   String sessionid = ((HttpSession)FacesContext.getCurrentInstance()
           .getExternalContext()
           .getSession(false)).getId();
   boolean captchaResult = getCaptchaService().validateReponseForId(sessionid, getVerifyingCode());//验证图片验证码
     UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(
     getUserName(), getPassword());
   //验证用户名和密码
   Authentication auth = getAuthenticationManager().authenticate(authReq);
   String result = "failure";
   if(auth.isAuthenticated() == true && captchaResult)
   {//如果图片验证码和帐户全部验证通过
    result = "succeed";
    //把验证信息保存的上下文中
    SecurityContext secCtx = SecurityContextHolder.getContext();
    secCtx.setAuthentication(auth);
   }
   
   return result;
  }
  catch(AuthenticationException e)
  {
   e.printStackTrace();
   return "failure";
  }
 }
}
CaptchaImageBean:
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.octo.captcha.service.image.ImageCaptchaService;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public class CaptchaImageBean {
 static final long serialVersionUID = 1L;
 private ImageCaptchaService jcaptchaService;

 public void setJcaptchaService(ImageCaptchaService jcaptchaService) {
  this.jcaptchaService = jcaptchaService;
 }

 public void paint(OutputStream out, Object data) throws IOException
 {
  byte[] captchaChallengeAsJpeg = null;
  ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();

  HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance()
        .getExternalContext().getRequest();

  String captchaId = ((HttpSession)FacesContext.getCurrentInstance()
    .getExternalContext()
    .getSession(false)).getId();

  BufferedImage challenge = jcaptchaService.getImageChallengeForID(
    captchaId, request.getLocale());
  JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);
  jpegEncoder.encode(challenge);
  captchaChallengeAsJpeg = jpegOutputStream.toByteArray();

  jpegOutputStream.close();
  out.write(captchaChallengeAsJpeg);
  out.flush();
  out.close();
 }
}
    最后当然是配置faces-config.xml了,里面配置了和Spring的整合、导航、managed bean等等:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>
 <application>
  <variable-resolver>
   org.springframework.web.jsf.DelegatingVariableResolver
  </variable-resolver>
 </application>
 <managed-bean>
  <managed-bean-name>loginBean</managed-bean-name>
  <managed-bean-class>com.fifthlab.demo.backingbean.LoginBean</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
   <property-name>authenticationManager</property-name>
   <value>#{authenticationManager}</value>
  </managed-property>
  <managed-property>
   <property-name>captchaService</property-name>
   <value>#{captchaService}</value>
  </managed-property>
 </managed-bean>

 <managed-bean>
  <managed-bean-name>captchaImageBean</managed-bean-name>
  <managed-bean-class>com.fifthlab.demo.backingbean.CaptchaImageBean</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
   <property-name>jcaptchaService</property-name>
   <value>#{jcaptchaService}</value>
  </managed-property>
 </managed-bean>

 <navigation-rule>
  <from-view-id>/login.jsp</from-view-id>
  <navigation-case>
   <from-outcome>succeed</from-outcome>
   <to-view-id>/success.jsp</to-view-id>
  </navigation-case>
  <navigation-case>
   <from-outcome>failure</from-outcome>
   <to-view-id>/login.jsp</to-view-id>
  </navigation-case>
 </navigation-rule>
</faces-config>




 
    大功告成!让我们来看一下演示吧



 


 










    填好帐户和验证码后,点击Submit,如果失败则还是该页不变,如果成功则跳转到:




需要的各种lib列表如下图:
 

 
















原文:http://farmerinchina.blog.sohu.com/70466803.html