gpt4 book ai didi

java - Spring事务性包私有(private)方法

转载 作者:塔克拉玛干 更新时间:2023-11-02 20:01:11 27 4
gpt4 key购买 nike

我有一个Spring MVC应用程序,其中的所有逻辑都与单个Java包(控制器,服务,存储库,DTO和资源)中的单个业务相关。我通过使表示,服务和持久层之间的所有方法都私有(不使用任何接口)来实现这一点。注意:层分离是通过具有可选依赖项的Maven模块强制执行的(表示层看不到持久层)。

但是,存储库也应该是@Transactional,并且使用Spring的默认值(添加spring-tx Maven依赖项+声明@EnableTransactionManagement +创建new DataSourceTransactionManager(dataSource) @Bean)是不够的:如果存储库没有至少一个公共方法(在集成测试中使用AopUtils.isAopProxy()对此进行检查)。

使用基于Maven +基于注释的Spring + Tomcat解决此问题的最直接方法(最小示例)是什么? (我听说过AspectJ并愿意在其他解决方案满足需要时避免使用它,因为AspectJ看起来设置复杂且与Lombok不兼容-但我想我可以用@AutoValue,自定义方面,Spring Roo等替换它。 )

编辑:我尝试使用AspectJ并可以到目前为止仅使用package-private方法(使用编译时编织)将方面(仅使用@Aspect即不涉及任何事务)添加到package-private类中。我目前在尝试对@Transactional做同样的事情。当我公开该类及其方法并定义@EnableTransactionalManagement时,它就起作用了(getCurrentTransactionName()显示了一些东西)。但是,一旦我更改为@EnableTransactionalManagement(mode = ASPECTJ),即使该类及其方法保持公共状态,它也不再起作用(getCurrentTransactionName()显示null)。注意:使用AspectJ模式时,proxyTargetClass不相关。

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

最佳答案

首先,警告:这是骇客,是仿制药的噩梦!我认为,要满足您仅在存储库中仅使用包私有方法的要求,就太麻烦了。

首先,定义一个可以使用的抽象实体:

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->此仓库将负责
  • 的实体的密钥类型
  • E->此仓库将负责
  • 的实体类型
  • T->将通过内部类向Spring公开的repo的类型,以便可以进行Spring代理机制,同时将方法的包私有保留在封闭类
  • U是将扩展此AbstractRepo的子类的类型

  • 这些通用类型参数是使您的具体存储库正常工作所必需的,并且是类型安全的,这意味着如果您尝试使用错误的类型,则它们将不会编译。

    之后,在 private @PostConstruct方法中,我们获得第三个泛型类型param T的类,这是将通过内部类向Spring公开的存储库的类型。我们需要这个 Class<T>,以便我们可以要求Spring给我们提供此类的bean。然后,我们将此bean分配给 delegate属性,即 private,并将通过 protected repo()方法进行访问。

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

    关于基本操作,我仅草绘了 load(),它接收要加载的实体的ID(即 K类型的ID),并返回安全键入的实体。

    到目前为止,一切都很好。您需要将这2个类放在应用程序的所有其他包和模块都可以访问的包和模块中,因为它们将用作您的具体实体和仓库的基类。

    现在,在您的应用程序的一个特定包中,定义一个示例实体:
    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,尽管根据您的要求,它的方法都是包私有的。

    在此具体的 SampleRepo类中未实现这些程序包专用方法。相反,它们是通过继承的 protected repo()方法委托给要由Spring扫描的内部类的。

    此内部类不是 public。它的范围是包专用的,因此它对于包外部的类不可见。但是,它的方法是 public,因此Spring可以使用代理对其进行拦截。根据您的需要,此内部类用 @Repository@Transactional注释。它扩展 AbstractRepo.SpringAbstractRepo内部类有两个原因:
  • 所有基本操作都会自动继承(例如load())。
  • 对于代理,Spring需要该类具有一个构造函数,该构造函数接收封闭类的Bean,并且此参数必须为@Autowired。否则,Spring将无法加载应用程序。由于AbstractRepo.SpringAbstractRepo abstract内部类只有一个构造函数,并且此构造函数接受一个参数,该参数必须是其AbstractRepo abstract封闭类的后代,因此AbstractRepo.SpringAbstractRepo内部类的每个后代都需要在其自己的构造函数中使用super(),并传递一个实例。相应的封闭类。这是由泛型强制执行的,因此,如果尝试传递错误类型的参数,则会出现编译错误。

  • 最后, abstract类不是必须的。您可以完全避免使用它们以及所有这些泛型的东西,尽管您最终将拥有重复的代码。

    关于java - Spring事务性包私有(private)方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28962770/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com