本文介绍了Spring事务包 - 私有方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a Spring MVC app with all logic related to a single business concern within a single Java package (controller, service, repository, DTO and resource). I enforce this by making all methods across presentation, service and persistence layers package-private (no interfaces are used). NB: layer separation is enforced with Maven modules with optional dependencies (presentation layer does not see persistence layer).

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

However, the repository shall also be @Transactional, and using Spring defaults (adding spring-tx Maven dependency + declaring @EnableTransactionManagement + creating a new DataSourceTransactionManager(dataSource) @Bean) isn't enough: the repository is no more proxified when it does not have at least one public method (I check this with AopUtils.isAopProxy() in an integration test).

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

What is the most straightforward way (minimal example) to solve this with Maven + annotation-based Spring + Tomcat? (I heard about AspectJ and would prefer to avoid it if another solution fits the need, because AspectJ seems complex to set up and is incompatible with Lombok --but I guess I could replace it with @AutoValue, custom aspects, Spring Roo, etc.)

编辑:我试图使用AspectJ并且到目前为止可以添加方面(仅使用 @Aspect 即没有所涉及的任何事务)只有package-private方法的package-private类(使用编译时编织)。我目前卡住了尝试用 @Transactional 做同样的事情。当我将类及其方法设为public并定义 @EnableTransactionalManagement 时,它可以工作( getCurrentTransactionName()显示一些内容)。但是一旦我更改为 @EnableTransactionalManagement(mode = ASPECTJ),它就不再起作用,即使该类及其方法仍然公开( getCurrentTransactionName()显示 null )。注意: proxyTargetClass 在使用AspectJ模式时无关紧要。

I attempted to use AspectJ and could so far add aspects (only using @Aspect i.e. without any transactions involved) to a package-private class with only package-private methods (using compile-time weaving). I'm currently stuck trying to do the same with @Transactional. When I make the class and its methods public and define @EnableTransactionalManagement, it works (getCurrentTransactionName() shows something). But as soon as I change to @EnableTransactionalManagement(mode = ASPECTJ), it does not work any more, even when the class and its methods remain public (getCurrentTransactionName() shows null). NB: proxyTargetClass is irrelevant when using AspectJ mode.

EDIT2:好的,我设法用AspectJ来解决这个问题,两者都是使用编译时和加载时编织。我缺少的关键信息是:package-private方法不从类注释继承事务信息,你必须把<$关于package-private方法本身的c $ c> @Transactional 。

OK I managed to solve this with AspectJ, both with compile-time and load-time weaving. The critical information I was missing was the JavaDoc of AnnotationTransactionAspect: package-private methods do not inherit transactional information from class annotations, you must put @Transactional on the package-private method itself.

推荐答案

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

First of all, a warning: this is a hack and a generics nightmare! Too much hassle, in my opinion, to satisfy your requirement of having only package-private methods in your repositories.

首先,定义一个抽象实体来使用:

First, define an abstract entity to work with:

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
}

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

This is just an abstract entity with a generic key.

然后,定义一个与抽象实体一起工作的抽象存储库,被所有其他存储库扩展。这引入了一些泛型魔术,所以要注意:

Then, define an abstract repository that works with abstract entities, which will be extended by all other repositories. This introduces some generics magic, so pay attention:

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种泛型类型进行参数化:

Please read the comments. The code is ugly, since it has a lot of generics. This AbstractRepo is parameterized with 4 generic types:


  • K - >类型该回购将被收取的实体的关键

  • E - >此回购将被收取的实体的类型

  • T - >将通过内部类向Spring公开的repo的类型,以便可以在将方法package-private保存在封闭类中时执行Spring代理机制。

  • U是类型将扩展此 AbstractRepo

  • K -> type of the key of the entity this repo will be in charged of
  • E -> type of the entity this repo will be in charged of
  • T -> type of the repo that will be exposed to Spring through an inner class, so that Spring proxying mechanism can take place while keeping your methods package-private in the enclosing class
  • U is the type of the subclass that will be extending this AbstractRepo

的子类需要这些通用类型参数为了使你的具体的repos工作并且是类型安全的,这意味着如果你试图使用错误的类型它们将无法编译。

These generic type params are needed in order to make your concrete repos work and be type-safe, meaning they won't compile if you attempt to use a wrong type.

之后,在 private @PostConstruct 方法,我们得到第三个泛型类型参数的类 T ,这是将通过内部类向Spring公开的repo的类型。我们需要这个类< T> ,这样我们就可以让Spring给我们这个类的bean。然后,我们将此bean分配给委托属性,该属性为 private ,并将通过<$ c $进行访问c> protected repo()方法。

After that, in a private @PostConstruct method, we get the class of the third generic type param T, which is the type of the repo that will be exposed to Spring through the inner class. We need this Class<T>, so that we can ask Spring to give us a bean of this class. Then, we assign this bean to the delegate attribute, which is private and will be accessied via the protected repo() method.

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

At the end, there's the inner class whose descendants will be proxied by Spring. It defines some generic type constraints and some basic operations. It has a special constructor that does some generics magic in order to get the class of the entity. You'll need the class of the entity later, either to pass it to your ORM (maybe a Hibernate Session) or to create an instance of your entity by reflection and fill it with data retrieved from the database (maybe a basic JDBC approach or Spring JDBC).

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

Regarding basic operations, I've only sketched load(), which receives the id of the entity to load, being this id of type K, and returns the entity safely typed.

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

So far so good. You'd need to put these 2 classes in a package and module reachable from all other packages and modules of your application, since they will be used as the base classes for your concrete entities and repos.

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

Now, in one specific package of your app, define a sample entity:

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;
    }
}

这只是一个<$ c的样本实体$ c> data 字段,其id为 Long

This is just a sample entity with a data field, whose id is of type Long.

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

Finally, define a concrete SampleRepo that manages instances of 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 ,但根据您的要求,它的方法都是包私有的。

Again, read carefully the comments in the code. This SampleRepo is available to Spring component scanning via the @Component annotation. It is public, though it's methods are all package-private, as per your requirement.

这些包私有在具体的 SampleRepo 类中没有实现方法。相反,它们通过继承的 protected repo()方法委托给要扫描的内部类。弹簧。

These package-private methods are not implemented in this concrete SampleRepo class. Instead, they're delegated via the inherited protected repo() method to the inner class that is to be scanned by Spring.

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

This inner class is not public. Its scope is package-private, so that it is not visible to classes outside the package. However, its methods are public, so that Spring can intercept them with proxies. This inner class is annotated with @Repository and @Transactional, as per your needs. It extends AbstractRepo.SpringAbstractRepo inner class for two reasons:


  1. 所有基本操作都是自动继承(例如 load())。

  2. 对于代理,Spring需要这个类有一个接收bean的构造函数封闭类,此参数必须是 @Autowired 。否则,Spring无法加载应用程序。由于 AbstractRepo.SpringAbstractRepo abstract 内部类只有一个构造函数,并且此构造函数接受必须是后代的参数其 AbstractRepo abstract 封闭类, AbstractRepo.SpringAbstractRepo 内部类需要在自己的构造函数中使用 super(),传递相应封闭类的实例。这是由泛型强制执行的,因此如果您尝试传递错误类型的参数,则会出现编译错误。

  1. All basic operations are automatically inherited (such as load()).
  2. For proxying, Spring needs this class to have a constructor that receives a bean of the enclosing class, and this argument must be @Autowired. Otherwise, Spring fails to load the application. As the AbstractRepo.SpringAbstractRepo abstract inner class has only one constructor, and this constructor accepts an argument that must be a descendant of its AbstractRepo abstract enclosing class, every descendant of the AbstractRepo.SpringAbstractRepo inner class will need to use super() in its own constructor, passing an instance of the corresponding enclosing class. This is enforced by generics, so if you attempt to pass an argument of the wrong type, you get a compilation error.

作为最终评论,抽象类不是必须的。你可以完全避免它们,以及所有这些泛型的东西,尽管你最终会有重复的代码。

As a final comment, the abstract classes are not a must. You could perfectly avoid them, as well as all this generics stuff, though you would end up having duplicate code.

这篇关于Spring事务包 - 私有方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 06:51
查看更多