标签归档:Spring

Spring 事务管理的一个 trick

问题

最近有同事碰到这个异常信息: Transaction rolled back because it has been marked as rollback-only ,异常栈被吃了,没打印出来。

调用代码大概如下:

@Component
public class InnerService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Throwable.class)
    public void innerTx(boolean ex) {
        jdbcTemplate.execute("insert into t_user(uname, age) values('liuwhb', 31)");
        if (ex) {
            throw new NullPointerException();
        }
    }

}

@Component
public class OutterService {
    @Autowired
    private InnerService innerService;

    @Transactional(rollbackFor = Throwable.class)
    public void outTx(boolean ex) {
        try {
            innerService.innerTx(ex);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

outterService.outTx(true);

他期望的是 innerService.innerTx(ex); 调用即使失败了也不会影响 OutterService.outTx 方法上的事务,只回滚了 innerTx 的操作。

结果没有得到他想要的,调用 OutterService.outTx 的外围方法捕获到了异常,异常信息是 Transaction rolled back because it has been marked as rollback-onlyoutTx 的其他操作也没有提交事务。

分析

上述方法的事务传播机制的默认的,也就是 Propagation.REQUIRED,如果当前已有事务就加入当前事务,没有就新建一个事务。

事务性的方法 outTx 调用了另一个事务性的方法 innerTx 。调用方对被调用的事务方法进行异常捕获,目的是希望被调用方的异常不会影响调用方的事务。

但还是会影响调用方的行为的。Spring 捕获到被调用事务方法的异常后,会把事务标记为 read-only,然后调用方提交事务的时候发现事务是只读的,就会抛出上面的异常。

继续阅读

接口与Spring自动注入

接口与自动注入

有业务接口 IService 和两种业务逻辑的实现 Aservice, Bservice 如下:

public interface IService {}

@Component
public class Aservice implements IService {}

@Component
public class Bservice implements IService {}

采用下面的方式自动注入时,会报错:

public class ManageService {
    @Autowired
    private Aservice aservice;

    @Autowired
    private Bservice bservice;

    // ...
}

异常信息类似为:nested exception is java.lang.IllegalArgumentException: Can not set xxx.ManageService field xxx.ManageService.aservice to com.sun.proxy.$Proxy48

继续阅读

Spring AOP 与 事务实现

1. AOP 与方法调用

对于 接口 interface,可以通过 java.lang.reflect.Proxy 来生成动态代理对象;
对于 类 class,可以通过字节码技术,如 CBLIB 来生成一个子类作为代理对象,此时类不能声明为 final 。

当 Spring 把 Bean 注入到另一个 Bean 时,其实注入的是它生成的代理对象,进行方法调用时,首先调用的是代理对象上的方法,代理对象的方法最终再调目标对象的方法,也就是开发人员编写的方法。Spring 通过在调用目标方法前后做处理来实现一些特性,例如事务管理、缓存等。

Spring 的 AOP 是基于对代理对象的方法调用的拦截的,只能拦截外部对象 对 某个对象的方法的调用,对象的方法调用同一个类的方法是不会被拦截的。

@Service
public class SpringTxService {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(SpringTxService.class);

    @Transactional(propagation = Propagation.REQUIRED)
    public void add() {
        LOGGER.info("do add");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requireNew() {
        LOGGER.info("do requireNew");
    }

    public void composite() {
        add();
        requireNew();
    }
}

上面的代码是基于注解进行事务控制的,composite 方法没有声明事务属性,它会调用 add、 requireNew
方法。当把 SpringTxService 注入到另一个 BeanB:
1. 在 BeanB 的方法里调用代理对象 composite 时,最终执行 add、 requireNew 是没有在事务里执行的,只是普通的方法调用。
2. 在 BeanB 的方法里调用代理对象的 add、 requireNew 方法时,这两个方法都会分别在相应的事务里执行。

当发现基于 AOP 实现的特性没有预期的效果时,一定要看看是不是在代理对象上调用还是目标类内部的方法调用。

继续阅读

Spring 导入资源文件

Spring 有多种方式可以导入资源文件,可以把这些属性文件里的属性值注入到 bean 的属性上。

1. 通过 <context:property-placeholder> 标签导入

通过这种方式导入的所有属性在同一个命名空间下,如果多个属性文件里有相同的属性名,以先导入的属性文件的属性为准,不会出现覆盖。可以通过 @Value("${propName}") 的方式注入到 bean 的属性上。

这种方式不能对属性文件指定 id,没法引用指定属性文件的属性,如果有同名的属性,引用到的总是第一个导入的属性。

配置如下:

<!-- 导入外部的资源文件,可以通过 @Value("${propName}") 的方式注入到 bean 的属性上 。 -->
<context:property-placeholder
    location="classpath:/learn/properties/1.properties"
    ignore-unresolvable="true" />
<!-- 要使用 context:property-placeholder 导入多个资源文件,必须指定 ignore-unresolvable="true" -->
<!-- 多个属性文件中,属性名相同的,以第一个加载的为准,不会出现后面加载的覆盖前面的 -->
<context:property-placeholder location="learn/properties/2.properties"
    ignore-unresolvable="true" />

继续阅读

最近处理的两个坑:Spring 启动问题与 log4j 配置

Spring 依赖注入问题

近半年,在生产环境老是出现应用在 JBoss 里启动不来,在下面的方法输出日志、进入循环后就走不出那个循环了:

Spring 3.0.5: org.springframework.beans.factory.support.DefaultListableBeanFactory

public void preInstantiateSingletons() throws BeansException {
     if (this.logger.isInfoEnabled()) {
          this.logger.info("Pre-instantiating singletons in " + this);
     }

     synchronized (this.beanDefinitionMap) {
          for (String beanName : this.beanDefinitionNames) {
               RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
               if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                    if (isFactoryBean(beanName)) {
                         final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName);
                         boolean isEagerInit;
                         if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                              isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                                   public Boolean run() {
                                        return ((SmartFactoryBean) factory).isEagerInit();
                                   }
                              }, getAccessControlContext());
                         }
                         else {
                              isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit(); 
                         }
                         if (isEagerInit) {
                              getBean(beanName);
                         }
                    }
                    else {
                         getBean(beanName);
                    }
               }
          }
     }
}

因为是生产环境,没法直接远程 debug,只能 dump 出栈和堆来分析,从栈来看,Spring 一直在做 bean 实例化;从堆来看,是这样的:

Top elements include:

•13,701 × Error creating bean with name ‘sqlSessionFactory’ … > (496 bytes)
•13,732 × Could not autowire method: public final void org.m… (336 bytes)
…….//还有很多其他的类创建失败

这个问题有一定的随机性,因为不是总是起不来,重启多次之后就可能顺利启动了,搞得每次发版本都胆战心惊。

继续阅读

Spring MVC 与 web开发

项目组用了 Spring MVC 进行开发,觉得对里面的使用方式不是很满意,就想,如果是我来搭建开发环境,我会怎么做?下面就是我的想法,只关注于 MVC 的 View 层。

一、统一的响应格式

现在基本上都是用 ajax 来调用后台接口,拿到 json格式的数据再展示,有的人直接返回数据,却没有考虑异常的情况,我觉得返回的报文里必须包含表示可能的异常信息的数据和业务响应数据。我定义了下面这个类来表示报文格式:

/**
 * 统一的 HTTP 响应格式。<br/>
 * code 为 "ok" 表示业务调用成功,否则是失败的错误码,如果有多个则以逗号分隔。<br/>
 * data 是业务数据,如果失败了则是 null。
 * 
 * @author http://coderbee.net
 *
 */
public class RespBody {
    public static final String OK_CODE = "ok";
    private final String code;
    private final Object data;

    private static final RespBody OK = new RespBody(OK_CODE, null);

    private RespBody(String code, Object data) {
        this.code = code;
        this.data = data;
    }

    public static RespBody ok() {
        return OK;
    }

    public static RespBody ok(Object data) {
        return new RespBody("ok", data);
    }

    public static RespBody error(String code) {
        return new RespBody(code, null);
    }

    public static RespBody error(String code, Object msg) {
        return new RespBody(code, msg);
    }

    public String getCode() {
        return code;
    }

    public Object getData() {
        return data;
    }
}

这个类提供了一些静态方法来快速构建响应报文,这也是很重要的一个设计:用静态工厂方法而不是构造函数。

这里的 code 不应该是直接的错误提示信息,应该只是简单的错误编码,这样不同的客户端都可以调用这个 API,然后再根据错误编码、客户端语言和自己的客户端特性选择合适的错误提示信息和提示方式。

继续阅读