Spring依存関係を挿入するをJPA EntityListenerにしようとしています。これが私のリスナークラスです。
@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class PliListener {
@Autowired
private EvenementPliRepository evenementPliRepository;
@PostPersist
void onPostPersist(Pli pli) {
EvenementPli ev = new EvenementPli();
ev.setPli(pli);
ev.setDateCreation(new Date());
ev.setType(TypeEvenement.creation);
ev.setMessage("Création d'un pli");
System.out.println("evenementPliRepository: " + evenementPliRepository);
evenementPliRepository.save(ev);
}
}
これが私のEntityクラスです:
@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...
ただし、私の依存関係(つまり、evenementPliRepository
)常にnullです。
誰でも助けていただけますか?
ステートレスBeanに依存関係を注入するハックは、依存関係を「静的」として定義し、Spterが依存関係を注入できるようにセッターメソッドを作成することです(静的依存関係に割り当てる)。
依存関係を静的として宣言します。
static private EvenementPliRepository evenementPliRepository;
Springが注入できるようにメソッドを作成します。
@Autowired
public void init(EvenementPliRepository evenementPliRepository)
{
MyListenerClass.evenementPliRepository = evenementPliRepository;
logger.info("Initializing with dependency ["+ evenementPliRepository +"]");
}
詳細: http://blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html
これは実際には古い質問ですが、別の解決策を見つけました:
public class MyEntityListener {
@Autowired
private ApplicationEventPublisher publisher;
@PostPersist
public void postPersist(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnCreatedEvent<>(this, target));
}
@PostUpdate
public void postUpdate(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnUpdatedEvent<>(this, target));
}
@PostRemove
public void postDelete(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnDeletedEvent<>(this, target));
}
}
おそらく最良のものではありませんが、AOP +ウィービングなしの静的変数よりも優れています。
そして、このソリューションはどうですか?
@MappedSuperclass
@EntityListeners(AbstractEntityListener.class)
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "creation_date")
private Date creationDate;
@Column(name = "modification_date")
private Date modificationDate;
}
次に、リスナー...
@Component
public class AbstractEntityListener {
@Autowired
private DateTimeService dateTimeService;
@PreUpdate
public void preUpdate(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
abstractEntity.setModificationDate(this.dateTimeService.getCurrentDate());
}
@PrePersist
public void prePersist(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
Date currentDate = this.dateTimeService.getCurrentDate();
abstractEntity.setCreationDate(currentDate);
abstractEntity.setModificationDate(currentDate);
}
}
そしてヘルパー...
/**
* Helper class which is able to autowire a specified class. It holds a static reference to the {@link org
* .springframework.context.ApplicationContext}.
*/
public final class AutowireHelper implements ApplicationContextAware {
private static final AutowireHelper INSTANCE = new AutowireHelper();
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
/**
* Tries to autowire the specified instance of the class if one of the specified beans which need to be autowired
* are null.
*
* @param classToAutowire the instance of the class which holds @Autowire annotations
* @param beansToAutowireInClass the beans which have the @Autowire annotation in the specified {#classToAutowire}
*/
public static void autowire(Object classToAutowire, Object... beansToAutowireInClass) {
for (Object bean : beansToAutowireInClass) {
if (bean == null) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
}
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
/**
* @return the singleton instance.
*/
public static AutowireHelper getInstance() {
return INSTANCE;
}
}
私のために働く。
ソース: http://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/
私は、AOPを使用してSpring BeanをEntityリスナーに注入する道を歩み始めました。 1日半の研究とさまざまなことを試した後、私はこれに遭遇しました link
Spring管理BeanをJPA EntityListenerクラスに注入することはできません。これは、JPAリスナーメカニズムがステートレスクラスに基づいている必要があるため、メソッドは事実上静的であり、コンテキストを認識しないためです。 ...実装は実際にインスタンスを作成するのではなく、クラスメソッドを使用するため、AOPの量はあなたを救いません。リスナーを表す「オブジェクト」には何も注入されません。
この時点で、私は再編成し、EclipseLink DescriptorEventAdapter を偶然見つけました。この情報を使用して、記述子アダプターを拡張するリスナークラスを作成しました。
public class EntityListener extends DescriptorEventAdapter {
private String injectedValue;
public void setInjectedValue(String value){
this.injectedValue = value;
}
@Override
public void aboutToInsert(DescriptorEvent event) {
// Do what you need here
}
}
クラスを使用するために、エンティティクラスで@EntityListenersアノテーションを使用できました。残念ながら、この方法ではSpringがリスナーの作成を制御できず、その結果、依存関係の注入が許可されません。代わりに、クラスに次の「init」関数を追加しました。
public void init() {
JpaEntityManager entityManager = null;
try {
// Create an entity manager for use in this function
entityManager = (JpaEntityManager) entityManagerFactory.createEntityManager();
// Use the entity manager to get a ClassDescriptor for the Entity class
ClassDescriptor desc =
entityManager.getSession().getClassDescriptor(<EntityClass>.class);
// Add this class as a listener to the class descriptor
desc.getEventManager().addListener(this);
} finally {
if (entityManager != null) {
// Cleanup the entity manager
entityManager.close();
}
}
}
少しのSpring XML構成を追加する
<!-- Define listener object -->
<bean id="entityListener" class="EntityListener " init-method="init">
<property name="injectedValue" value="Hello World"/>
<property name="entityManagerFactory" ref="emf"/>
</bean>
これで、Springがエンティティリスナを作成し、必要な依存関係を注入し、リスナオブジェクトがリッスンするエンティティクラスに自身を登録する状況になりました。
これがお役に立てば幸いです。
リスナーに@ Componentアノテーションを付けてから、non static setterを作成して、注入されたSpring Beanを割り当てます。
私のコードは次のようになります:
@Component
public class EntityListener {
private static MyService service;
@Autowired
public void setMyService (MyService service) {
this.service=service;
}
@PreUpdate
public void onPreUpdate() {
service.doThings()
}
@PrePersist
public void onPersist() {
...
}
}
https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/ で提案されているアプローチをテストし、機能しました。あまりきれいではありませんが、仕事をします。私のために少し変更されたAutowireHelperクラスは次のようになりました。
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class AutowireHelper implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
public static void autowire(Object classToAutowire) {
AutowireHelper.applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
}
次に、次のようにエンティティリスナーからこれを呼び出します。
public class MyEntityAccessListener {
@Autowired
private MyService myService;
@PostLoad
public void postLoad(Object target) {
AutowireHelper.autowire(this);
myService.doThings();
...
}
public void setMyService(MyService myService) {
this.myService = myService;
}
}
これは、このリスナーBeanがSpringの制御下にないためだと思います。 Springはそれをインスタンス化しないのですが、SpringはどのようにしてそのBeanを見つけてインジェクションを行うかを知ることができますか?
私はそれを試したことはありませんが、SpringのConfigurableアノテーションでAspectJ Weaverを使用して、Springがインスタンス化されていないBeanをSpringで制御できるようです。
JPAリスナーの問題は次のとおりです。
それらはSpringによって管理されていません(注入はありません)
それらは作成される(または作成される可能性がある)beforeSpringのApplication Contextの準備ができている(したがって、コンストラクター呼び出しでBeanを注入できない)
問題に対処するための私の回避策:
1)public static Listener
フィールドを持つLISTENERS
クラスを作成します。
public abstract class Listener {
// for encapsulation purposes we have private modifiable and public non-modifiable lists
private static final List<Listener> PRIVATE_LISTENERS = new ArrayList<>();
public static final List<Listener> LISTENERS = Collections.unmodifiableList(PRIVATE_LISTENERS);
protected Listener() {
PRIVATE_LISTENERS.add(this);
}
}
2)Listener.LISTENERS
に追加するすべてのJPAリスナーは、このクラスを拡張する必要があります。
public class MyListener extends Listener {
@PrePersist
public void onPersist() {
...
}
...
}
3)これで、SpringのApplication Contextの準備ができた直後に、すべてのリスナーを取得してBeanを注入できます。
@Component
public class ListenerInjector {
@Autowired
private ApplicationContext context;
@EventListener(ContextRefreshedEvent.class)
public void contextRefreshed() {
Listener.LISTENERS.forEach(listener -> context.getAutowireCapableBeanFactory().autowireBean(listener));
}
}
別のオプション:
AplicationContextをアクセス可能にするサービスを作成します。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import lombok.Setter;
@Service
class ContextWrapper {
@Setter
private static ApplicationContext context;
@Autowired
public ContextWrapper(ApplicationContext ac) {
setContext(ac);
}
public static ApplicationContext getContext() {
return context;
}
}
これを使って:
...
public class AuditListener {
private static final String AUDIT_REPOSITORY = "AuditRepository";
@PrePersist
public void beforePersist(Object object){
//TODO:
}
@PreUpdate
public void beforeUpdate(Object object){
//TODO:
}
@PreRemove
public void beforeDelete(Object object) {
getRepo().save(getAuditElement("DEL",object));
}
private Audit getAuditElement(String Operation,Object object){
Audit audit = new Audit();
audit.setActor("test");
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
audit.setDate(timestamp);
return audit;
}
private AuditRepository getRepo(){
return ContextWrapper.getContext().getBean(AUDIT_REPOSITORY, AuditRepository.class);
}
}
このクラスは、jpaからリスナーとして作成されます。
...
@Entity
@EntityListeners(AuditListener.class)
@NamedQuery(name="Customer.findAll", query="SELECT c FROM Customer c")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
...
リスナーはSpringの制御下にないため、コンテキストBeanにアクセスできません。複数のオプション(@Configurable(...))を試しましたが、コンテキストに静的にアクセスするクラスを作成する以外は機能しませんでした。すでにそのジレンマでは、これはエレガントなオプションだと思います。
Spring V5.1(およびHibernate V5.3)以降、Springはこれらのクラスのプロバイダーとして登録されるため、そのまま使用できます。 SpringBeanContainer のドキュメントを参照