Spring事务包 - 私有方法

时间:2015-03-10 11:39:24

标签: java spring maven spring-transactions

我有一个Spring MVC应用程序,其中包含与单个Java包(控制器,服务,存储库,DTO和资源)中的单个业务问题相关的所有逻辑。我通过使表示,服务和持久层的所有方法都使用package-private(不使用接口)来强制执行此操作。注意:使用具有可选依赖项的Maven模块强制执行层分离(表示层没有看到持久层)。

但是,存储库也应为@Transactional,并使用Spring默认值(添加spring-tx Maven依赖关系+声明@EnableTransactionManagement +创建new DataSourceTransactionManager(dataSource) @Bean)还不够:当存储库没有至少一个公共方法时,它就不再被代理了(我在集成测试中用AopUtils.isAopProxy()检查)。

使用Maven +基于注释的Spring + Tomcat解决这个问题最简单的方法(最简单的例子)是什么? (我听说过AspectJ,如果另一个解决方案符合需要,我宁愿避免使用它,因为AspectJ看起来很复杂并且与Lombok不兼容 - 但我想我可以用@AutoValue,自定义方面,Spring Roo等替换它。)

编辑:我尝试使用AspectJ,到目前为止,只能使用包私有方法(使用编译时编织)将方面(仅使用@Aspect,即不涉及任何事务)添加到包私有类。我目前仍在尝试对@Transactional做同样的事情。当我将类及其方法公开并定义@EnableTransactionalManagement时,它可以正常工作(getCurrentTransactionName()显示某些内容)。但是,只要我更改为@EnableTransactionalManagement(mode = ASPECTJ),它就不再有效,即使该类及其方法仍然公开(getCurrentTransactionName()显示null)。注意:proxyTargetClass在使用AspectJ模式时无关紧要。

EDIT2:好的,我设法用AspectJ来解决这个问题,包括编译时和加载时编织。我缺少的关键信息是AnnotationTransactionAspect的JavaDoc:package-private方法不从类注释继承事务信息,你必须将@Transactional放在package-private方法本身上。

1 个答案:

答案 0 :(得分:1)

首先,一个警告:这是一个黑客和一个泛型的噩梦!在我看来,太麻烦了,以满足您在存储库中只使用包私有方法的要求。

首先,定义要使用的抽象实体:

package reachable.from.everywhere;

import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@MappedSuperclass
public abstract class AbstractEntity<K> {

    @Id
    private K id;

    // TODO other attributes common to all entities & JPA annotations

    public K getId() {
        return this.id;
    }

    // TODO hashCode() and equals() based on id
}

这只是一个带有通用密钥的抽象实体。

然后,定义一个与抽象实体一起使用的抽象存储库,它将被所有其他存储库扩展。这介绍了一些仿制药,所以要注意:

package reachable.from.everywhere;

import java.lang.reflect.ParameterizedType;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

public abstract class AbstractRepo<
    K, // key
    E extends AbstractEntity<K>, // entity
    T extends AbstractRepo.SpringAbstractRepo<K, E, U>, // Spring repo
    U extends AbstractRepo<K, E, T, U>> { // self type

    @Autowired
    private ApplicationContext context;

    private T delegate;

    @SuppressWarnings("unchecked")
    @PostConstruct
    private void init() {
        ParameterizedType type = 
            (ParameterizedType) this.getClass().getGenericSuperclass();
        // Spring repo is inferred from 3rd param type
        Class<T> delegateClass = (Class<T>) type.getActualTypeArguments()[2];
        // get an instance of the matching Spring repo
        this.delegate = this.context.getBean(delegateClass);
    }

    protected T repo() {
        return this.delegate;
    }

    protected static abstract class SpringAbstractRepo<K, E, U> {

        protected final Class<E> entityClass;

        // force subclasses to invoke this constructor
        // receives an instance of the enclosing class
        // this is just for type inference and also
        // because Spring needs subclasses to have
        // a constructor that receives the enclosing class
        @SuppressWarnings("unchecked")
        protected SpringAbstractRepo(U outerRepo) {
            ParameterizedType type = 
                (ParameterizedType) this.getClass().getGenericSuperclass();
            // Spring repo is inferred from 3rd param type
            this.entityClass = (Class<E>) type.getActualTypeArguments()[1];
        }

        public E load(K id) {
            // this method will be forced to be transactional!
            E entity = ...; 
            // TODO load entity with key = id from database
            return entity;
        }

        // TODO other basic operations
    }
}

请阅读评论。代码很难看,因为它有很多泛型。此AbstractRepo参数化为4种泛型:

  • K - &gt;此回购将负责的实体的密钥类型
  • E - &gt;该仓库将负责的实体的类型
  • T - &gt;将通过内部类向Spring公开的repo的类型,以便可以在将方法package-private保存在封闭类中时执行Spring代理机制
  • U是将扩展此AbstractRepo
  • 的子类的类型

为了使您的具体存储库工作并且类型安全,需要这些通用类型参数,这意味着如果您尝试使用错误的类型,它们将无法编译。

之后,在private @PostConstruct方法中,我们得到第三个泛型类型param T的类,它是将通过Spring公开的repo的类型内在的阶级。我们需要这个Class<T>,以便我们可以让Spring给我们这个类的bean。然后,我们将此bean分配给delegate属性private,并通过protected repo()方法进行访问。

最后,有一个内部类,其后代将由Spring代理。它定义了一些泛型类型约束和一些基本操作。它有一个特殊的构造函数,可以执行一些泛型魔法以获取实体的类。稍后你需要实体的类,要么将它传递给你的ORM(可能是一个Hibernate Session),要么通过反射创建你的实体实例,并用从数据库中检索的数据填充它(也许是基本的JDBC方法或Spring JDBC)。

关于基本操作,我只绘制了load(),它接收要加载的实体的id,是K类型的id,并返回实体安全输入。

到目前为止一切顺利。您需要将这两个类放在一个包中,并且可以从应用程序的所有其他包和模块中访问它们,因为它们将被用作具体实体和repos的基类。


现在,在您应用的一个特定包中,定义一个示例实体:

package sample;

import reachable.from.everywhere.AbstractEntity;

public class SampleEntity
    extends AbstractEntity<Long> {

    private String data;

    public String getData() {
        return this.data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

这只是一个带有data字段的示例实体,其ID类型为Long

最后,定义一个管理SampleRepo的实例的具体SampleEntity

package sample;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import reachable.from.everywhere.AbstractRepo;

// annotation needed to detect inner class bean
@Component
public class SampleRepo
    extends AbstractRepo<
        Long, // key
        SampleEntity, // entity with id of type Long 
        SampleRepo.SampleSpringRepo,  // Spring concrete repo
        SampleRepo> { // self type

    // here's your package-private method
    String method(String s) {
        return this.repo().method(s);
    }

    // here's another package-private method
    String anotherMethod(String s) {
        return this.repo().anotherMethod(s);
    }

    // can't be public
    // otherwise would be visible from other packages
    @Repository
    @Transactional
    class SampleSpringRepo
        extends AbstractRepo.SpringAbstractRepo<
            Long, // same as enclosing class 1st param
            SampleEntity, // same as enclosing class 2nd param
            SampleRepo> { // same as enclosing class 4th param

        // constructor and annotation needed for proxying
        @Autowired
        public SampleSpringRepo(SampleRepo myRepo) {
            super(myRepo);
        }

        public String method(String arg) {
            // transactional method
            return "method - within transaction - " + arg;
        }

        public String anotherMethod(String arg) {
            // transactional method
            return "anotherMethod - within transaction - " + arg;
        }
    }
}

再次仔细阅读代码中的注释。此SampleRepo可通过@Component注释进行Spring组件扫描。它是public,但根据您的要求,它的方法都是包私有的。

这个package-private方法没有在这个具体的SampleRepo类中实现。相反,它们通过继承的protected repo()方法委托给要由Spring扫描的内部类。

此内部类不是public。它的范围是包私有的,因此它对包外的类是不可见的。但是,它的方法是public,因此Spring可以使用代理拦截它们。根据您的需要,此内部类使用@Repository@Transactional进行注释。它扩展了AbstractRepo.SpringAbstractRepo内部类,原因有两个:

  1. 所有基本操作都会自动继承(例如load())。
  2. 对于代理,Spring需要这个类有一个接收封闭类的bean的构造函数,并且该参数必须是@Autowired。否则,Spring无法加载应用程序。由于AbstractRepo.SpringAbstractRepo abstract内部类只有一个构造函数,并且此构造函数接受的参数必须是其AbstractRepo abstract封闭类的后代,因此AbstractRepo.SpringAbstractRepo super()封闭类的后代{1}}内部类需要在自己的构造函数中使用abstract,传递相应封闭类的实例。这是由泛型强制执行的,因此如果您尝试传递错误类型的参数,则会出现编译错误。
  3. 作为最终评论,{{1}}类不是必须的。你可以完全避免它们,以及所有这些泛型的东西,尽管你最终会有重复的代码。