首先,警告:这是一个黑客和泛型的噩梦!在我看来,要满足您在存储库中只有包私有方法的要求太麻烦了。
首先,定义一个要使用的抽象实体:
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
}
}
请阅读 cmets。代码很丑,因为它有很多泛型。这个AbstractRepo 用 4 种泛型类型参数化:
- K -> 此 repo 将负责的实体的密钥类型
- E -> 此 repo 将负责的实体的类型
- T -> 将通过内部类向 Spring 公开的 repo 类型,以便 Spring 代理机制可以发生,同时将您的方法包私有在封闭类中
- U 是要扩展此
AbstractRepo 的子类的类型
需要这些泛型类型参数才能使您的具体存储库正常工作并保证类型安全,这意味着如果您尝试使用错误的类型,它们将无法编译。
之后,在private@PostConstruct 方法中,我们得到第三个泛型类型参数T 的类,这是将通过内部类公开给Spring 的repo 的类型。我们需要这个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;
}
}
}
再次,仔细阅读代码中的 cmets。这个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 类不是必须的。你可以完全避免它们,以及所有这些泛型的东西,尽管你最终会得到重复的代码。