简体   繁体   English

Spring事务包 - 私有方法

[英]Spring transactional package-private method

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). 我有一个Spring MVC应用程序,其中包含与单个Java包(控制器,服务,存储库,DTO和资源)中的单个业务问题相关的所有逻辑。 I enforce this by making all methods across presentation, service and persistence layers package-private (no interfaces are used). 我通过使表示,服务和持久层的所有方法都使用package-private(不使用接口)来强制执行此操作。 NB: layer separation is enforced with Maven modules with optional dependencies (presentation layer does not see persistence layer). 注意:使用具有可选依赖项的Maven模块强制执行层分离(表示层没有看到持久层)。

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

What is the most straightforward way (minimal example) to solve this with Maven + annotation-based Spring + Tomcat? 使用Maven +基于注释的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,如果另一个解决方案符合需要,我宁愿避免使用它,因为AspectJ看起来很复杂并且与Lombok不兼容 - 但我想我可以用@AutoValue,自定义方面,Spring Roo等替换它。)

EDIT: I attempted to use AspectJ and could so far add aspects (only using @Aspect ie without any transactions involved) to a package-private class with only package-private methods (using compile-time weaving). 编辑:我试图使用AspectJ,并且到目前为止可以添加方面(仅使用@Aspect即不涉及任何事务)到包私有类(仅使用包私有方法)(使用编译时编织)。 I'm currently stuck trying to do the same with @Transactional . 我目前正试图用@Transactional做同样的事情。 When I make the class and its methods public and define @EnableTransactionalManagement , it works ( getCurrentTransactionName() shows something). 当我将类及其方法公开并定义@EnableTransactionalManagement ,它可以工作( getCurrentTransactionName()显示一些东西)。 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 ). 但是一旦我改为@EnableTransactionalManagement(mode = ASPECTJ) ,它就不再起作用,即使该类及其方法仍然是公共的( getCurrentTransactionName()显示为null )。 NB: proxyTargetClass is irrelevant when using AspectJ mode. 注意:使用AspectJ模式时, proxyTargetClass无关紧要。

EDIT2: OK I managed to solve this with AspectJ, both with compile-time and load-time weaving. EDIT2:好的,我设法用AspectJ来解决这个问题,包括编译时和加载时编织。 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. 我缺少的关键信息是AnnotationTransactionAspect的JavaDoc:package-private方法不从类注释继承事务信息,你必须在package-private方法本身上放置@Transactional

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

Please read the comments. 请阅读评论。 The code is ugly, since it has a lot of generics. 代码很难看,因为它有很多泛型。 This AbstractRepo is parameterized with 4 generic types: AbstractRepo使用4种泛型类型进行参数化:

  • K -> type of the key of the entity this repo will be in charged of K - >这个回购将被收取的实体的密钥类型
  • E -> type of the entity this repo will be in charged of E - >此回购将被收取的实体的类型
  • 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 T - >将通过内部类向Spring公开的repo的类型,以便Spring代理机制可以在保持方法package-private的情况下发生在封闭类中
  • U is the type of the subclass that will be extending this AbstractRepo U是将扩展此AbstractRepo的子类的类型

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. 这些泛型类型参数是需要的,以便使您的具体repos工作并且是类型安全的,这意味着如果您尝试使用错误的类型,它们将无法编译。

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. 之后,在private @PostConstruct方法中,我们得到第三个泛型类型param T ,它是将通过内部类向Spring公开的repo的类型。 We need this Class<T> , so that we can ask Spring to give us a bean of this class. 我们需要这个Class<T> ,这样我们就可以让Spring给我们一个这个类的bean。 Then, we assign this bean to the delegate attribute, which is private and will be accessied via the protected repo() method. 然后,我们将此bean分配给delegate属性,该属性是private ,并将通过protected repo()方法进行访问。

At the end, there's the inner class whose descendants will be proxied by Spring. 最后,有一个内部类,其后代将由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). 您稍后需要实体的类,要么将其传递给您的ORM(可能是Hibernate Session ),要么通过反射创建实体的实例,并用从数据库中检索的数据填充它(可能是基本的JDBC方法或者Spring JDBC)。

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. 关于基本操作,我只绘制了load() ,它接收要加载的实体的id,是这个类型为K id,并返回实体安全输入。

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. 您需要将这两个类放在一个包中,并且可以从应用程序的所有其他包和模块访问它们,因为它们将用作具体实体和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;
    }
}

This is just a sample entity with a data field, whose id is of type Long . 这只是一个带有data字段的示例实体,其id为Long类型。

Finally, define a concrete SampleRepo that manages instances of SampleEntity : 最后,定义一个管理SampleEntity实例的具体SampleRepo

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

Again, read carefully the comments in the code. 再次,仔细阅读代码中的注释。 This SampleRepo is available to Spring component scanning via the @Component annotation. SampleRepo可通过@Component注释进行Spring组件扫描。 It is public , though it's methods are all package-private, as per your requirement. 它是public ,但根据您的要求,它的方法都是包私有的。

These package-private methods are not implemented in this concrete SampleRepo class. 这个package-private方法没有在这个具体的SampleRepo类中实现。 Instead, they're delegated via the inherited protected repo() method to the inner class that is to be scanned by Spring. 相反,它们通过继承的protected repo()方法委托给要由Spring扫描的内部类。

This inner class is not public . 这个内部阶级不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. 但是,它的方法是public ,因此Spring可以使用代理拦截它们。 This inner class is annotated with @Repository and @Transactional , as per your needs. 根据您的需要,此内部类使用@Repository@Transactional注释。 It extends AbstractRepo.SpringAbstractRepo inner class for two reasons: 它扩展了AbstractRepo.SpringAbstractRepo内部类,原因有两个:

  1. All basic operations are automatically inherited (such as load() ). 所有基本操作都会自动继承(例如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 . 对于代理,Spring需要这个类有一个接收封闭类的bean的构造函数,并且该参数必须是@Autowired Otherwise, Spring fails to load the application. 否则,Spring无法加载应用程序。 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. 由于AbstractRepo.SpringAbstractRepo abstract内部类只有一个构造函数,并且这个构造函数接受一个必须是其AbstractRepo abstract封闭类的后代的参数,所以AbstractRepo.SpringAbstractRepo内部类的每个后代都需要使用自己的super()构造函数,传递相应封闭类的实例。 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. 作为最后的评论, abstract类不是必须的。 You could perfectly avoid them, as well as all this generics stuff, though you would end up having duplicate code. 你可以完全避免它们,以及所有这些泛型的东西,尽管你最终会有重复的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 对于@Transactional方法,禁用警告“Access可以是包私有” - Disable warning “Access can be package-private” for @Transactional methods Package-private方法似乎没有被覆盖 - Package-private method seems to not be overriden Spring @Autowired 字段 - 哪个访问修饰符是私有的还是包私有的? - Spring @Autowired fields - which access modifier, private or package-private? Spring在测试期间从package-private类获取配置 - Spring gets config from package-private class during test 使用CGLib拦截JDK类的package-private方法 - Interception of package-private method for JDK class with CGLib Java - 包私有类中的方法可访问性? - Java - Method accessibility inside package-private class? Method.invoke无法使用程序包专用类 - Method.invoke not working with package-private class 在 Jersey 2 中调用代理对象的包私有方法的奇怪行为 - Strange behavior calling package-private method of proxied object in Jersey 2 在Eclipse中将package-private变量更改为private - Change package-private variable to private in Eclipse 为什么不能在同一个包中调用另一个类的包私有方法呢? - Why can't I call a package-private method of another class that's a subclass of a class in the same package?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM