05月22, 2018

记一个PostConstruct和Spring的坑

记一个PostConstruct和Spring的坑

场景

在一些业务中抽象了一个业务类型,所有子类在启动程序时注册到一个context中,然后通过每种业务的code在context来获取具体的子类实现。

抽象业务父类

/**
 * @author wpy
 * @date 2018/4/18 15:42
 */
public abstract class AbstractBusiness {

    @PostConstruct
    public void inti(){
        BusinessContext.registerBusiness(getBusinessType(),this);
    }

    /**
     * 获取当前业务类型
     * @return
     */
    public abstract int getBusinessType();

    /**
     * 业务流程
     * @param msg
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public abstract ResultData exec(String msg) throws Exception;
}

context上下文

/**
 * @author wpy
 * @date 2018/4/18 15:43
 */
public class BusinessContext {
    private static Map<Integer,AbstractBusiness> map = new HashMap<>();


    public static void registerBusiness(Integer businessType,AbstractBusiness business){
        map.put(businessType,business);
    }

    public static AbstractBusiness getBusiness(Integer businessType){
        return map.get(businessType);
    }
}

问题

我在父类的exec方法上加了@Transactional注解,系统中也配置了事务,但是在所有的子类的exec方法中事务并没有生效。

排查原因

直接使用注入子类的方式和原来通过BusinessContext.getBusiness的方式进行对比,发现直接注入时事务生效,通过BusinessContext.getBusiness的方式事务未生效。

原因定位到,父类的初始化方法

@PostConstruct
public void inti(){
    BusinessContext.registerBusiness(getBusinessType(),this);
}

我们先看看@PostConstruct的说明

PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用 PostConstruct 注释的方法也必须被调用。只有一个方法可以用此注释进行注释。应用 PostConstruct 注释的方法必须遵守以下所有标准:该方法不得有任何参数,除非是在 EJB 拦截器 (interceptor) 的情况下,根据 EJB 规范的定义,在这种情况下它将带有一个 InvocationContext 对象 ;该方法的返回类型必须为 void;该方法不得抛出已检查异常;应用 PostConstruct 的方法可以是 public、protected、package private 或 private;除了应用程序客户端之外,该方法不能是 static;该方法可以是 final;如果该方法抛出未检查异常,那么不得将类放入服务中,除非是能够处理异常并可从中恢复的 EJB。

说明这个方法会在被Spring创建并且初始化后会调用这个方法,我在这个方法中把自己注册到了BusinessContext中,但是是这里注册的是this,并不是Spring创建的代理对象,所以事物切面并不会生效!

解决办法

this替换成从Spring中创建的代理对象即可。

@Resource
private ApplicationContext applicationContext;

@PostConstruct
public void inti(){
    BusinessContext.registerBusiness(getBusinessType(),applicationContext.getBean(this.getClass()));
}

本文链接:https://www.qiangshuidiyu.xin/post/postconstruct.html

-- EOF --

Comments